看博文之前,希望大家先打開自己的微信點到朋友圈中去,仔細觀察是不是發現朋友圈裏的有個“九宮格”的圖片區域,點擊圖片又會跳到圖片的詳細查看頁面,並且支持圖片的滑動和縮放?這個功能是不是很常用呢?!那麼我今天正好做了這個Demo,下面爲大家講解一下。首先按照慣例先看一下效果圖吧,尤其不會錄製gif動畫(哎~沒辦法,模擬器不支持多點觸控,剛好我的手機又沒有Root,不能錄屏,悲催啊,大家見諒,想要看真實效果的話,煩請移到博文最下方,點擊下載源碼,運行後再看效果哈~~),這裏先就拿幾張靜態的圖片頂替一下好了。見諒!
主頁ListView的效果: 點擊九宮格圖片跳轉到大圖 多點觸控,縮放圖片
效果嘛,將就着看吧!實在看不明白就想想微信朋友圈,或者拖到下方,點擊下載源碼!這裏,首先分析一下主界面吧,佈局都是很簡單的,主界面僅僅就是一個ListView的控件,ListView的Item上值得注意的是,Item上包含了一個GridView,這個GridView唄用作實現“九宮格”的效果,主界面佈局就是一個ListView,這裏不說了,我們先來看看ListView的Item的佈局吧,以下是item_list.xml
1 2 3 4 5 6 7 8 9 10 11 12 | <!--?xml version= 1.0 encoding=utf- 8 ?--> <relativelayout android:layout_height= "match_parent" android:layout_width= "match_parent" android:paddingbottom= "5dp" android:paddingtop= "5dp" xmlns:android= "http://schemas.android.com/apk/res/android" > <imageview android:background= "@drawable/ic_launcher" android:id= "@+id/iv_avatar" android:layout_height= "50dp" android:layout_width= "50dp" android:scaletype= "centerCrop" > <textview android:id= "@+id/tv_title" android:layout_height= "wrap_content" android:layout_marginleft= "5dp" android:layout_torightof= "@id/iv_avatar" android:layout_width= "wrap_content" android:text= "爺,今天心情好!" android:textsize= "16sp" > <textview android:id= "@+id/tv_content" android:layout_below= "@+id/tv_title" android:layout_height= "wrap_content" android:layout_marginleft= "5dp" android:layout_margintop= "3dp" android:layout_torightof= "@id/iv_avatar" android:layout_width= "wrap_content" android:text= "今天又是霧霾!" android:textsize= "16sp" > <com.example.imagedemo.noscrollgridview android:columnwidth= "70dp" android:gravity= "center" android:horizontalspacing= "2.5dp" android:id= "@+id/gridview" android:layout_below= "@id/tv_content" android:layout_height= "wrap_content" android:layout_marginleft= "5dp" android:layout_margintop= "3dp" android:layout_torightof= "@id/iv_avatar" android:layout_width= "220dp" android:numcolumns= "3" android:stretchmode= "columnWidth" android:verticalspacing= "2.5dp" > </com.example.imagedemo.noscrollgridview></textview></textview></imageview></relativelayout> |
好了,大家看到了,佈局也是極其簡單的,但是有個問題就是ListView嵌套進了GridView,那麼就會出現一個問題,導致GridView顯示的不全,那麼該怎麼解決這個問題呢?其實也簡單,就是重寫一個GridView,測量一下GridView的高度,再設置上去。具體解決方案請看上篇博文ListView嵌套GridView顯示不全解決方法或者源碼,如下NoScrollGridView.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | 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); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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; } ... }</string></string> |
好了,有了ListView,那麼不可避免的就是做Item上的數據適配了。繼承一個BaseAdapter,代碼如下,都比較簡單:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 | /** * 首頁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; } }</string></string></itementity></itementity> |
這裏有需要解釋的地方了,看看listview上的圖片處理,由於圖片都是從網絡獲取的,爲了避免圖片過多造成OOM,那麼這裏加載圖片的時候必不可少的需要做內存優化,圖片的優化方式有很多,我這裏採取了最簡單最直接得方式,使用了開源的ImageLoader這個圖片加載框架,這個框架簡直是太優秀了,減少了開發者一系列不必要而且時常會出現的麻煩,關於ImageLoader並不是本篇博文需要講解的知識,關於ImageLoader,歡迎在GitHub主頁上下載,地址是https://github.com/nostra13/Android-Universal-Image-Loader,既然使用了ImageLoader這個框架,就不得不在程序上做一些初始化的操作,首先需要自定義一個全局的上下文Application類,將ImageLoader的相關屬性初始化上去,直接看代碼好了,見名知意:MyApplication.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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); } } |
1 | android:name=com.example.imagedemo.MyApplication |
此外由於ImageLoader是網絡獲取圖片,又需要本地sdcard緩存圖片,所以需要加上一下的權限,這是Imageloader標準權限:
1 2 3 | <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" ></uses-permission></uses-permission></uses-permission> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | ...... @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; } ...... |
1 2 3 4 5 6 7 8 | <!--?xml version= 1.0 encoding=utf- 8 ?--> <framelayout android:layout_height= "match_parent" android:layout_width= "match_parent" xmlns:android= "http://schemas.android.com/apk/res/android" > <com.example.imagedemo.hackyviewpager android:background= "@android:color/black" android:id= "@+id/pager" android:layout_height= "match_parent" android:layout_width= "match_parent" > <textview android:background= "@android:color/transparent" android:gravity= "center" android:id= "@+id/indicator" android:layout_gravity= "bottom" android:layout_height= "wrap_content" android:layout_width= "match_parent" android:text= "@string/viewpager_indicator" android:textcolor= "@android:color/white" android:textsize= "18sp" > </textview></com.example.imagedemo.hackyviewpager></framelayout> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | 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 ; } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | /** * 圖片查看器 */ 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); } } } </string></string></string> |
已知圖片查看的界面是繼承自FragmentActivity的,所以支持顯示的界面必須需要Fragment來實現,那麼就自定義個Frangment吧,用這個Fragment來從url中獲取圖片資源,顯示圖片。image_detail_fragment.xml
1 2 3 4 5 6 7 8 | <!--?xml version= 1.0 encoding=utf- 8 ?--> <framelayout android:background= "@android:color/black" android:layout_height= "match_parent" android:layout_width= "match_parent" xmlns:android= "http://schemas.android.com/apk/res/android" > <imageview android:adjustviewbounds= "true" android:contentdescription= "@string/app_name" android:id= "@+id/image" android:layout_height= "match_parent" android:layout_width= "match_parent" android:scaletype= "centerCrop" > <progressbar android:id= "@+id/loading" android:layout_gravity= "center" android:layout_height= "wrap_content" android:layout_width= "wrap_content" android:visibility= "gone" > </progressbar></imageview></framelayout> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 | /** * 單張圖片顯示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如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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是怎麼實現的,查看以下部分源碼:
1 2 3 4 5 6 | // 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 ]; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | /** * 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); } } } |