Android利用HorizontalScrollView仿ViewPager设计简单相册
最近学习了一个视频公开课,讲到了利用HorizontalScrollView仿ViewPager设计的一个简单相册,其实主要用了ViewPager缓存的思想。此篇文章参考:Android自定义HorizontalScrollView打造超强Gallery效果(这篇文章与公开课的讲的大致一样)
这里简单说一下ViewPager的缓存机制
1.进入ViewPager时,加载当前页和后一页;
2.当滑动ViewPager至下一页时,加载后一页,此时第一页是不会销毁的,同时加载当前页的下一页。
其实就是默认加载3页,当前页,前一页和后一页。
而此HorizontalScrollView是默认加载两页的,这个要注意,不然调度代码会让人晕。
话不多说,上代码:
代码结构如下图:
一个View,一个Adapter,一个MainActivity,相信不用解释,大家也相当清楚了,典型的MVC模式~
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 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 | package com.ssa.horizontalscrollview.myview; import java.util.HashMap; import java.util.Map; import com.ssa.horizontalscrollview.myUtils.DisplayUtil; import android.content.Context; import android.graphics.Color; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.View.OnClickListener; import android.widget.HorizontalScrollView; import android.widget.LinearLayout; public class GalleryHorizontalScrollView extends HorizontalScrollView implements OnClickListener { private LinearLayout mContainer; // MyHorizontalScrollView中的LinearLayout private int mChildWidth; // 子元素的宽度 private int mChildHeight; // 子元素的高度 private int mAllLastIndex; // 当前的最后一张的index private int mdisplayLastIndex; // 当前显示的最后一张的index private int mAllFirstIndex; // 当前的第一张index private GalleryHorizontalScrollViewAdapter mAdapter; // 数据适配器 private int mScreenWidth; // 屏幕的宽度 private int mCountOneScreen; private Map<View, Integer> mViewPos = new HashMap<View, Integer>(); private OnCurrentImageChangeListener mOnCurrentImageChangeListener; private OnClickImageChangeListener mOnClickImageChangeListener; public void setmOnCurrentImageChangeListener( OnCurrentImageChangeListener mListener) { this.mOnCurrentImageChangeListener = mListener; } public void setmOnClickImageListener(OnClickImageChangeListener mListener) { this.mOnClickImageChangeListener = mListener; } /** * 图片滚动时回调接口 */ public interface OnCurrentImageChangeListener { void onCurrentImgChanged(int position, View view); } /** * 点击图片时回调接口 */ public interface OnClickImageChangeListener { void onClickImageChangeListener(int position, View view); } public GalleryHorizontalScrollView(Context context, AttributeSet attrs) { super(context, attrs); // 获取屏幕宽度 mScreenWidth = getResources().getDisplayMetrics().widthPixels; } /** * 初始化数据,设置适配器 */ public void initData(GalleryHorizontalScrollViewAdapter mAdapter) { this.mAdapter = mAdapter; mContainer = (LinearLayout) getChildAt(0); final View view = mAdapter.getView(0, null, mContainer); mContainer.addView(view); if (mChildHeight == 0 && mChildWidth == 0) { /*int w = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); int h = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);*/ /** * 上面注释掉的是一位老师的写法,但我查了好多资料,用参数0和View.MeasureSpec.UNSPECIFIED是一种不太优美的做法; * 好的做法应该是 * 当View为match_parent时,无法测量出View的大小(任玉刚大神讲的,确实是这么一回事,这个具体的原因要结合源码分析,可以看一下任大神的博客) * 当View宽高为具体的数值时,比如100px: * int w =View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY); * int h =View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY); * view.measure(w, h); * 当View宽高为wrap_content时: * int w =View.MeasureSpec.makeMeasureSpec((1<<30)-1, View.MeasureSpec.AT_MOST); * int h =View.MeasureSpec.makeMeasureSpec((1<<30)-1, View.MeasureSpec.AT_MOST); * view.measure(w, h); * * 我的此View高度为固定的150dip,宽度为wrap_content */ int heightPx = DisplayUtil.dip2px(getContext(), 150); int w =View.MeasureSpec.makeMeasureSpec((1<<30)-1, View.MeasureSpec.AT_MOST); int h =View.MeasureSpec.makeMeasureSpec(heightPx, View.MeasureSpec.EXACTLY); view.measure(w, h); mChildHeight = view.getMeasuredHeight(); mChildWidth = view.getMeasuredWidth(); // 计算每次加载多少个item mdisplayLastIndex = mScreenWidth / mChildWidth; mCountOneScreen = mdisplayLastIndex + 1; initFirstScreenChildren(mdisplayLastIndex + 1); } } /** * 加载第一屏的元素 * * @param mDisplayCountOneScreen */ private void initFirstScreenChildren(int mDisplayCountOneScreen) { mContainer = (LinearLayout) getChildAt(0); mContainer.removeAllViews(); mViewPos.clear(); for (int i = 0; i < mDisplayCountOneScreen; i++) { View view = mAdapter.getView(i, null, mContainer); // 待完善的点击事件 view.setOnClickListener(this); mContainer.addView(view); mViewPos.put(view, i); mAllLastIndex = i; } // 初始化并刷新界面 if (null != mOnCurrentImageChangeListener) { notifyCurrentImgChanged(); } } private void notifyCurrentImgChanged() { // 先清除所有的背景颜色,点击时设置为蓝色 for (int i = 0; i < mContainer.getChildCount(); i++) { mContainer.getChildAt(i).setBackgroundColor(Color.WHITE); } mOnCurrentImageChangeListener.onCurrentImgChanged(mAllFirstIndex, mContainer.getChildAt(0)); } @Override public boolean onTouchEvent(MotionEvent ev) { /* * Log.e("X", getX()+""); Log.e("ChildX", * mContainer.getChildAt(0).getX()+""); Log.e("RawX",getLeft() +""); */ switch (ev.getAction()) { case MotionEvent.ACTION_MOVE: int scrollX = getScrollX(); Log.e( "ScrollX" , scrollX + "" ); if (scrollX >= mChildWidth) { // 加载下一页,移除第一张 loadNextImg(); } if (scrollX == 0) { // 加载上一页,移除最后一张 loadPreImg(); } break ; } return super.onTouchEvent(ev); } private void loadNextImg() { // 数组边界值计算 if (mAllLastIndex == mAdapter.getCount() - 1) { return ; } // 移除第一张图片,且将水平滚动位置置0 scrollTo(0, 0); mViewPos.remove(mContainer.getChildAt(0)); mContainer.removeViewAt(0); // 获取下一张图片,并且设置onclick事件,且加入容器中 View view = mAdapter.getView(++mAllLastIndex, null, mContainer); view.setOnClickListener(this); mContainer.addView(view); mViewPos.put(view, mAllLastIndex); // 当前第一张图片小标 mAllFirstIndex++; // 如果设置了滚动监听则触发 if (mOnCurrentImageChangeListener != null) { notifyCurrentImgChanged(); } } private void loadPreImg() { if (mAllFirstIndex == 0) { return ; } int index = mAllLastIndex - mCountOneScreen; if (index >= 0) { // 移除最后一张 int oldViewPos = mContainer.getChildCount() - 1; mViewPos.remove(mContainer.getChildAt(oldViewPos)); mContainer.removeViewAt(oldViewPos); // 将加入的View放在第一个位置 View view = mAdapter.getView(index, null, mContainer); mViewPos.put(view, index); mContainer.addView(view, 0); view.setOnClickListener(this); // 水平滚动位置向左移动View的宽度的像素 scrollTo(mChildWidth, 0); mAllLastIndex--; mAllFirstIndex--; if (null != mOnCurrentImageChangeListener) { notifyCurrentImgChanged(); } } } @Override public void onClick(View v) { if (null!=mOnClickImageChangeListener){ mOnClickImageChangeListener.onClickImageChangeListener(mViewPos.get(v), v); } } } |
下面是Adapter的源码:
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 | package com.ssa.horizontalscrollview.myview; import java.util.List; import com.ssa.horizontalscrollview.R; import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; public class GalleryHorizontalScrollViewAdapter { private LayoutInflater mInflater; private List<Integer> mDatas; public GalleryHorizontalScrollViewAdapter(Context context, List<Integer> mDatas) { mInflater = LayoutInflater.from(context); this.mDatas = mDatas; } public Object getItem(int position) { return mDatas.get(position); } public long getItemId(int position) { return position; } public int getCount() { return mDatas.size(); } public View getView(int position, View contentView, ViewGroup parent) { ViewHolder myHolder = null; if (null == contentView) { contentView = mInflater.inflate(R.layout.activity_gallery_item, parent, false); myHolder = new ViewHolder(contentView); contentView.setTag(myHolder); } else { myHolder = (ViewHolder)contentView.getTag(); } myHolder.ivImg.setImageResource(mDatas.get(position)); myHolder.tvText.setText( "Img_" +position); return contentView; } private static class ViewHolder { ImageView ivImg; TextView tvText; public ViewHolder(View view) { ivImg = (ImageView)view.findViewById(R.id.iv_content); tvText =(TextView)view.findViewById(R.id.tv_index); } } } |
下面是MainActivity的源码:
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 | package com.ssa.horizontalscrollview; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import android.app.Activity; import android.graphics.Color; import android.os.Bundle; import android.view.View; import android.widget.ImageView; import com.ssa.horizontalscrollview.myview.GalleryHorizontalScrollView; import com.ssa.horizontalscrollview.myview.GalleryHorizontalScrollView.OnClickImageChangeListener; import com.ssa.horizontalscrollview.myview.GalleryHorizontalScrollView.OnCurrentImageChangeListener; import com.ssa.horizontalscrollview.myview.GalleryHorizontalScrollViewAdapter; public class MainActivity extends Activity { private GalleryHorizontalScrollView mHorizontalScrollView; private GalleryHorizontalScrollViewAdapter mAdapter; private ImageView mImg; private List<Integer> mDatas = new ArrayList<Integer>(Arrays.asList( R.drawable.a, R.drawable.b, R.drawable.c, R.drawable.d, R.drawable.e,R.drawable.f,R.drawable.g)); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mImg = (ImageView)findViewById(R.id.iv_content); mHorizontalScrollView = (GalleryHorizontalScrollView)findViewById(R.id.mhsv_gallery_container); mAdapter = new GalleryHorizontalScrollViewAdapter(this, mDatas); mHorizontalScrollView.setmOnCurrentImageChangeListener( new OnCurrentImageChangeListener() { @Override public void onCurrentImgChanged(int position, View view) { mImg.setImageResource(mDatas.get(position)); view.setBackgroundColor(Color.parseColor( "#6d9eeb" )); } }); mHorizontalScrollView.setmOnClickImageListener( new OnClickImageChangeListener() { @Override public void onClickImageChangeListener(int position, View view) { mImg.setImageResource(mDatas.get(position)); } }); mHorizontalScrollView.initData(mAdapter); } } |
至些,调试运行,读者会发现,整个相册会非常卡,
甚至有的图片还没有显示出来如img_4,看一下logcat,相信大家会发现原因:
信息已经提示的很清楚了,图片太大,
此时大家应该明白了,笔者故意选择了几张很大的图片加载,虽然没大到直接让应用崩掉,但是体验性已经变得非常差了,这是因为课堂上的老师讲课时用的图片都是几十K的小图片,加载当然不会有问题,所以要想使这个相册作为一个实用的相册,还要处理图片过大的问题,不然,依旧会造成OOM。
此时就用到这个工具类了:
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 | package com.ssa.horizontalscrollview.myUtils; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; public class BitmapUtil { public static Bitmap decodeSampledBitmapFromResources(Resources res, int resId, int reqWidth, int reqHeight) { final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeResource(res, resId, options); options.inSampleSize = calculateInsampleSize(options, reqWidth, reqHeight); options.inJustDecodeBounds = false; return BitmapFactory.decodeResource(res, resId, options); } public static int calculateInsampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { final int height = options.outHeight; final int width = options.outWidth; int inSampleSize = 1; if (height > reqHeight || width > reqWidth) { final int halfHeight = height / 2; final int halfWidth = width / 2; while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth) { inSampleSize *= 2; } } return inSampleSize; } } |
添加了这个工具类,上面几个类的代码也要略微修改一下,具体怎么改,大家可以下载下面我上传的源码:
至于效果如下动图所示(生成的gif图有点卡,大家可以运行看效果):
源码下载:HorizontalScrollView仿ViewPager设计相册
以上就是本文的全部内容,希望对大家学习Android软件编程有所帮助。