編輯:Android開發實例
再看文章之前,希望大家先打開自己的微信點到朋友圈中去,仔細觀察是不是發現朋友圈裡的有個“九宮格”的圖片區域,點擊圖片又會跳到圖片的詳細查看頁面,並且支持圖片的滑動和縮放?這個功能是不是很常用呢?!那麼我今天正好做了這個Demo,下面為大家講解一下。首先按照慣例先看一下效果圖吧,尤其不會錄制gif動畫(哎~沒辦法,模擬器不支持多點觸控,剛好我的手機又沒有Root,不能錄屏,悲催啊,大家見諒,想要看真實效果的話,煩請移到文章最下方轉載文章中進行源碼下載,點擊下載源碼,運行後再看效果哈~~),這裡先就拿幾張靜態的圖片頂替一下好了。見諒!

效果嘛,將就著看吧!實在看不明白就想想微信朋友圈,或者拖到下方,點擊下載源碼!這裡,首先分析一下主界面吧,布局都是很簡單的,主界面僅僅就是一個ListView的控件,ListView的Item上值得注意的是,Item上包含了一個GridView,這個GridView呗用作實現“九宮格”的效果,主界面布局就是一個ListView,這裡不說了,我們先來看看ListView的Item的布局吧,以下是item_list.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="5dp" android:paddingTop="5dp" > <ImageView android:id="@+id/iv_avatar" android:layout_width="50dp" android:layout_height="50dp" android:background="@drawable/ic_launcher" android:scaleType="centerCrop" /> <TextView android:id="@+id/tv_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="5dp" android:layout_toRightOf="@id/iv_avatar" android:text="爺,今天心情好!" android:textSize="16sp" /> <TextView android:id="@+id/tv_content" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/tv_title" android:layout_marginLeft="5dp" android:layout_marginTop="3dp" android:layout_toRightOf="@id/iv_avatar" android:text="今天又是霧霾!" android:textSize="16sp" /> <com.example.imagedemo.NoScrollGridView android:id="@+id/gridview" android:layout_width="220dp" android:layout_height="wrap_content" android:layout_below="@id/tv_content" android:layout_marginLeft="5dp" android:layout_marginTop="3dp" android:layout_toRightOf="@id/iv_avatar" android:columnWidth="70dp" android:gravity="center" android:horizontalSpacing="2.5dp" android:numColumns="3" android:stretchMode="columnWidth" android:verticalSpacing="2.5dp" /> </RelativeLayout>
好了,大家看到了,布局也是極其簡單的,但是有個問題就是ListView嵌套進了GridView,那麼就會出現一個問題,導致GridView顯示的不全,那麼該怎麼解決這個問題呢?其實也簡單,就是重寫一個GridView,測量一下GridView的高度,再設置上去。具體解決方案請看上篇博文ListView嵌套GridView顯示不全解決方法或者源碼,如下NoScrollGridView.java
package com.example.imagedemo;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.GridView;
/**
* 自定義的“九宮格”——用在顯示帖子詳情的圖片集合 解決的問題:GridView顯示不全,只顯示了一行的圖片,比較奇怪,嘗試重寫GridView來解決
*
* @author lichao
* @since 2014-10-16 16:41
*
*/
public class NoScrollGridView extends GridView {
public NoScrollGridView(Context context) {
super(context);
// TODO Auto-generated constructor stub
}
public NoScrollGridView(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}
public NoScrollGridView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// TODO Auto-generated method stub
int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,
MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
}
}
接下來看看ListView上面Item的實體是什麼樣的數據結構,這就顯得非常簡單了。
public class ItemEntity {
private String avatar; // 用戶頭像URL
private String title; // 標題
private String content; // 內容
private ArrayList<String> imageUrls; // 九宮格圖片的URL集合
public ItemEntity(String avatar, String title, String content,
ArrayList<String> imageUrls) {
super();
this.avatar = avatar;
this.title = title;
this.content = content;
this.imageUrls = imageUrls;
}
...
}
好了,有了ListView,那麼不可避免的就是做Item上的數據適配了。繼承一個BaseAdapter,代碼如下,都比較簡單:
/**
* 首頁ListView的數據適配器
*
* @author Administrator
*
*/
public class ListItemAdapter extends BaseAdapter {
private Context mContext;
private ArrayList<ItemEntity> items;
public ListItemAdapter(Context ctx, ArrayList<ItemEntity> items) {
this.mContext = ctx;
this.items = items;
}
@Override
public int getCount() {
return items == null ? 0 : items.size();
}
@Override
public Object getItem(int position) {
return items.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
holder = new ViewHolder();
convertView = View.inflate(mContext, R.layout.item_list, null);
holder.iv_avatar = (ImageView) convertView
.findViewById(R.id.iv_avatar);
holder.tv_title = (TextView) convertView
.findViewById(R.id.tv_title);
holder.tv_content = (TextView) convertView
.findViewById(R.id.tv_content);
holder.gridview = (NoScrollGridView) convertView
.findViewById(R.id.gridview);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
ItemEntity itemEntity = items.get(position);
holder.tv_title.setText(itemEntity.getTitle());
holder.tv_content.setText(itemEntity.getContent());
// 使用ImageLoader加載網絡圖片
DisplayImageOptions options = new DisplayImageOptions.Builder()//
.showImageOnLoading(R.drawable.ic_launcher) // 加載中顯示的默認圖片
.showImageOnFail(R.drawable.ic_launcher) // 設置加載失敗的默認圖片
.cacheInMemory(true) // 內存緩存
.cacheOnDisk(true) // sdcard緩存
.bitmapConfig(Config.RGB_565)// 設置最低配置
.build();//
ImageLoader.getInstance().displayImage(itemEntity.getAvatar(),
holder.iv_avatar, options);
final ArrayList<String> imageUrls = itemEntity.getImageUrls();
if (imageUrls == null || imageUrls.size() == 0) { // 沒有圖片資源就隱藏GridView
holder.gridview.setVisibility(View.GONE);
} else {
holder.gridview.setAdapter(new NoScrollGridAdapter(mContext,
imageUrls));
}
// 點擊回帖九宮格,查看大圖
holder.gridview.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
// TODO Auto-generated method stub
imageBrower(position, imageUrls);
}
});
return convertView;
}
/**
* 打開圖片查看器
*
* @param position
* @param urls2
*/
protected void imageBrower(int position, ArrayList<String> urls2) {
Intent intent = new Intent(mContext, ImagePagerActivity.class);
// 圖片url,為了演示這裡使用常量,一般從數據庫中或網絡中獲取
intent.putExtra(ImagePagerActivity.EXTRA_IMAGE_URLS, urls2);
intent.putExtra(ImagePagerActivity.EXTRA_IMAGE_INDEX, position);
mContext.startActivity(intent);
}
/**
* listview組件復用,防止“卡頓”
*
* @author Administrator
*
*/
class ViewHolder {
private ImageView iv_avatar;
private TextView tv_title;
private TextView tv_content;
private NoScrollGridView gridview;
}
}
這裡有需要解釋的地方了,看看listview上的圖片處理,由於圖片都是從網絡獲取的,為了避免圖片過多造成OOM,那麼這裡加載圖片的時候必不可少的需要做內存優化,圖片的優化方式有很多,我這裡采取了最簡單最直接得方式,使用了開源的ImageLoader這個圖片加載框架,這個框架簡直是太優秀了,減少了開發者一系列不必要而且時常會出現的麻煩,關於ImageLoader並不是本篇博文需要講解的知識,關於ImageLoader,歡迎在GitHub主頁上下載,地址是https://github.com/nostra13/Android-Universal-Image-Loader,既然使用了ImageLoader這個框架,就不得不在程序上做一些初始化的操作,首先需要自定義一個全局的上下文Application類,將ImageLoader的相關屬性初始化上去,直接看代碼好了,見名知意:MyApplication.java
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
DisplayImageOptions defaultOptions = new DisplayImageOptions.Builder() //
.showImageForEmptyUri(R.drawable.ic_launcher) //
.showImageOnFail(R.drawable.ic_launcher) //
.cacheInMemory(true) //
.cacheOnDisk(true) //
.build();//
ImageLoaderConfiguration config = new ImageLoaderConfiguration//
.Builder(getApplicationContext())//
.defaultDisplayImageOptions(defaultOptions)//
.discCacheSize(50 * 1024 * 1024)//
.discCacheFileCount(100)// 緩存一百張圖片
.writeDebugLogs()//
.build();//
ImageLoader.getInstance().init(config);
}
}
定義這個Application之後,需要在清單文件中配置一下,在Manifest.xml中的Application節點上添加:
android:name="com.example.imagedemo.MyApplication"
此外由於ImageLoader是網絡獲取圖片,又需要本地sdcard緩存圖片,所以需要加上一下的權限,這是Imageloader標准權限:
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
再看看上面的Item上數據,裡面有個GridView,顯然這個GridView也是需要做數據適配的,這個數據反應的是從網絡加載圖片,比較簡單,看代碼NoScrollGridAdapter.java
......
Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = View.inflate(ctx, R.layout.item_gridview, null);
ImageView imageView = (ImageView) view.findViewById(R.id.iv_image);
DisplayImageOptions options = new DisplayImageOptions.Builder()//
.cacheInMemory(true)//
.cacheOnDisk(true)//
.bitmapConfig(Config.RGB_565)//
.build();
ImageLoader.getInstance().displayImage(imageUrls.get(position),
imageView, options);
return view;
}
......
這樣,所有的數據適配就做好了,接下來就需要做圖片查看器了,當我們點擊ListView上Item裡的“九宮格”——NoScrollGridView的某張圖片的時候,需要把這個圖片的url傳給一個圖片查看器,圖片查看器裡會根據傳遞進來的url去網絡加載這張圖片,那麼其實圖片查看器就是一個新的單獨的Activity,這個Activity會包含一個ViewPager,用來管理多張圖片的查看。image_detail_pager.xml
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <com.example.imagedemo.HackyViewPager android:id="@+id/pager" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/black" /> <TextView android:id="@+id/indicator" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="bottom" android:background="@android:color/transparent" android:gravity="center" android:text="@string/viewpager_indicator" android:textColor="@android:color/white" android:textSize="18sp" /> </FrameLayout>
HackyViewPager.java
public class HackyViewPager extends ViewPager {
private static final String TAG = "HackyViewPager";
public HackyViewPager(Context context) {
super(context);
}
public HackyViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
try {
return super.onInterceptTouchEvent(ev);
} catch (IllegalArgumentException e) {
// 不理會
Log.e(TAG, "hacky viewpager error1");
return false;
} catch (ArrayIndexOutOfBoundsException e) {
// 不理會
Log.e(TAG, "hacky viewpager error2");
return false;
}
}
}
ImagePagerActivity.java
/**
* 圖片查看器
*/
public class ImagePagerActivity extends FragmentActivity {
private static final String STATE_POSITION = "STATE_POSITION";
public static final String EXTRA_IMAGE_INDEX = "image_index";
public static final String EXTRA_IMAGE_URLS = "image_urls";
private HackyViewPager mPager;
private int pagerPosition;
private TextView indicator;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.image_detail_pager);
pagerPosition = getIntent().getIntExtra(EXTRA_IMAGE_INDEX, 0);
ArrayList<String> urls = getIntent().getStringArrayListExtra(
EXTRA_IMAGE_URLS);
mPager = (HackyViewPager) findViewById(R.id.pager);
ImagePagerAdapter mAdapter = new ImagePagerAdapter(
getSupportFragmentManager(), urls);
mPager.setAdapter(mAdapter);
indicator = (TextView) findViewById(R.id.indicator);
CharSequence text = getString(R.string.viewpager_indicator, 1, mPager
.getAdapter().getCount());
indicator.setText(text);
// 更新下標
mPager.setOnPageChangeListener(new OnPageChangeListener() {
@Override
public void onPageScrollStateChanged(int arg0) {
}
@Override
public void onPageScrolled(int arg0, float arg1, int arg2) {
}
@Override
public void onPageSelected(int arg0) {
CharSequence text = getString(R.string.viewpager_indicator,
arg0 + 1, mPager.getAdapter().getCount());
indicator.setText(text);
}
});
if (savedInstanceState != null) {
pagerPosition = savedInstanceState.getInt(STATE_POSITION);
}
mPager.setCurrentItem(pagerPosition);
}
@Override
public void onSaveInstanceState(Bundle outState) {
outState.putInt(STATE_POSITION, mPager.getCurrentItem());
}
private class ImagePagerAdapter extends FragmentStatePagerAdapter {
public ArrayList<String> fileList;
public ImagePagerAdapter(FragmentManager fm, ArrayList<String> fileList) {
super(fm);
this.fileList = fileList;
}
@Override
public int getCount() {
return fileList == null ? 0 : fileList.size();
}
@Override
public Fragment getItem(int position) {
String url = fileList.get(position);
return ImageDetailFragment.newInstance(url);
}
}
}
已知圖片查看的界面是繼承自FragmentActivity的,所以支持顯示的界面必須需要Fragment來實現,那麼就自定義個Frangment吧,用這個Fragment來從url中獲取圖片資源,顯示圖片。image_detail_fragment.xml
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/black" > <ImageView android:id="@+id/image" android:layout_width="match_parent" android:layout_height="match_parent" android:adjustViewBounds="true" android:contentDescription="@string/app_name" android:scaleType="centerCrop" /> <ProgressBar android:id="@+id/loading" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:visibility="gone" /> </FrameLayout>
ImageDetailFragment.java
/**
* 單張圖片顯示Fragment
*/
public class ImageDetailFragment extends Fragment {
private String mImageUrl;
private ImageView mImageView;
private ProgressBar progressBar;
private PhotoViewAttacher mAttacher;
public static ImageDetailFragment newInstance(String imageUrl) {
final ImageDetailFragment f = new ImageDetailFragment();
final Bundle args = new Bundle();
args.putString("url", imageUrl);
f.setArguments(args);
return f;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mImageUrl = getArguments() != null ? getArguments().getString("url")
: null;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
final View v = inflater.inflate(R.layout.image_detail_fragment,
container, false);
mImageView = (ImageView) v.findViewById(R.id.image);
mAttacher = new PhotoViewAttacher(mImageView);
mAttacher.setOnPhotoTapListener(new OnPhotoTapListener() {
@Override
public void onPhotoTap(View arg0, float arg1, float arg2) {
getActivity().finish();
}
});
progressBar = (ProgressBar) v.findViewById(R.id.loading);
return v;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
ImageLoader.getInstance().displayImage(mImageUrl, mImageView,
new SimpleImageLoadingListener() {
@Override
public void onLoadingStarted(String imageUri, View view) {
progressBar.setVisibility(View.VISIBLE);
}
@Override
public void onLoadingFailed(String imageUri, View view,
FailReason failReason) {
String message = null;
switch (failReason.getType()) {
case IO_ERROR:
message = "下載錯誤";
break;
case DECODING_ERROR:
message = "圖片無法顯示";
break;
case NETWORK_DENIED:
message = "網絡有問題,無法下載";
break;
case OUT_OF_MEMORY:
message = "圖片太大無法顯示";
break;
case UNKNOWN:
message = "未知的錯誤";
break;
}
Toast.makeText(getActivity(), message,
Toast.LENGTH_SHORT).show();
progressBar.setVisibility(View.GONE);
}
@Override
public void onLoadingComplete(String imageUri, View view,
Bitmap loadedImage) {
progressBar.setVisibility(View.GONE);
mAttacher.update();
}
});
}
}
寫到這裡,此篇博文也宣告結束了。需要提出的是,我這裡的圖片查看器實現的圖片的縮放效果使用的是開源組件PhotoView,關於PhotoView的github項目地址在這裡,https://github.com/chrisbanes/PhotoView 需要點進去這個項目的網址,去下載源碼,將源碼全部拷貝到項目中來,使用也是相當方便的,demo如下:
ImageView mImageView;
PhotoViewAttacher mAttacher;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Any implementation of ImageView can be used!
mImageView = (ImageView) findViewById(R.id.iv_photo);
// Set the Drawable displayed
Drawable bitmap = getResources().getDrawable(R.drawable.wallpaper);
mImageView.setImageDrawable(bitmap);
// Attach a PhotoViewAttacher, which takes care of all of the zooming functionality.
mAttacher = new PhotoViewAttacher(mImageView);
}
// If you later call mImageView.setImageDrawable/setImageBitmap/setImageResource/etc then you just need to call
attacher.update();
剛開始這個圖片查看器是我自己自定義View來實現的,其實需要實現圖片的手勢識別+多點觸控+縮放,是可以使用矩陣Matrix來實現的,只不過這樣顯得特別的麻煩不說,而且極易出現BUG,這對於某些“急功近利”的項目來說,是個不好的兆頭。所以,我這裡摒棄了我用Matrix自定義的效果,改用github大牛為我們寫好的開源組件,這樣效率就上去了,大家也可以用Matrix自己去實現一下圖片的多點觸摸縮放的效果,關於Matrix的學習,請參加我以前的博文,Android自定義控件——3D畫廊和圖像矩陣。其實關於android上的圖片縮放真沒什麼其它的方式,唯一能使用的還是Matrix這個類,不信先來瞧瞧Github大牛寫的開源組件PhotoView是怎麼實現的,查看以下部分源碼:
// These are set so we don't keep allocating them on the heap private final Matrix mBaseMatrix = new Matrix(); private final Matrix mDrawMatrix = new Matrix(); private final Matrix mSuppMatrix = new Matrix(); private final RectF mDisplayRect = new RectF(); private final float[] mMatrixValues = new float[9];
/**
* Set's the ImageView's ScaleType to Matrix.
*/
private static void setImageViewScaleTypeMatrix(ImageView imageView) {
/**
* PhotoView sets it's own ScaleType to Matrix, then diverts all calls
* setScaleType to this.setScaleType automatically.
*/
if (null != imageView && !(imageView instanceof IPhotoView)) {
if (!ScaleType.MATRIX.equals(imageView.getScaleType())) {
imageView.setScaleType(ScaleType.MATRIX);
}
}
}
以上只是PhotoView的部分源碼,一目了然的發現它的實現也是基於Matrix的,時間與篇幅的局限性,大家需要更好的了解PhotoView的實現的話,就下載它的源碼查看吧,要理解大神的想法是需要一些扎實的基礎,關於PhotoView的具體實現細節,我也弄不太明白,可能是我對Matrix了解的不深刻吧,希望以後加強學習,也希望以後跟你們交流學習,共同進步!
本文轉載:http://blog.csdn.net/allen315410/article/details/40264551
Android控件系列之TextView使用介紹
學習目的: 1、了解在Android中如何使用TextView控件 2、掌握TextView控件重要屬性 作用:TextView類似一般UI中的Label,Tex
Android JSON解析器
JSON代表JavaScript對象符號。它是一個獨立的數據交換格式,是XML的最佳替代品。本章介紹了如何解析JSON文件,並從中提取所需的信息。Android提供了四個
Android仿微信朋友圈圖片查看器
再看文章之前,希望大家先打開自己的微信點到朋友圈中去,仔細觀察是不是發現朋友圈裡的有個“九宮格”的圖片區域,點擊圖片又會跳到圖片的詳細查看頁面,並且支持圖片的滑動
Android開發技巧之我的菜單我做主(自定義菜單)
Android SDK本身提供了一種默認創建菜單的機制。但通過這種機制創建的菜單雖然從功能上很完備,但在界面效果