Android 自定义 HorizontalScrollView 实现ViewPager效果,打造再多图片(控件)也不怕 OOM
2016-03-13 16:48
1001 查看
1. 效果
上次实现了这个IOS Reveal效果,能够确保中间部分是彩色其余部分是灰色,但是也遗留了两个问题:
(1)如果把它做成ViewPager效果的方式可能会更好;
(2)HorizontalScrollView没有重用机制,虽然可能我们并不会加载很多的图片,但是一旦过多肯定会出现OOM问题;
2.分析和实现
2.1实现ViewPager效果:
1.记录当前位置,跟踪触摸的实际事件,重载onTouch事件,获取滑动的速率,做出相应的处理:public RevealScrollView extends HorizontalScrollView { // 当前的位置 private int mCureentItem = 0; // 跟踪触摸实际事件,用来获取速率 private VelocityTracker mVelocityTracker; private int mMaximumVelocity; /** * 初始化 */ private void init() { mVelocityTracker = VelocityTracker.obtain(); mMaximumVelocity = ViewConfiguration.get(getContext()) .getScaledMaximumFlingVelocity(); } @Override public boolean onTouchEvent(MotionEvent event) { mVelocityTracker.addMovement(event); final int action = event.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: mFingerDownX = (int) event.getRawX(); mFingerDownY = (int) event.getRawY(); mVelocityTracker.clear(); mVelocityTracker.addMovement(event); return true; case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); int velocityX = (int) mVelocityTracker.getXVelocity(); mVelocityTracker.clear(); int fingerUpX = (int) event.getRawX(); int fingerDx = mFingerDownX - fingerUpX; int fingerUpY = (int) event.getRawY(); int yDiff = Math.abs(fingerUpY - mFingerDownY); int xDiff = Math.abs(fingerDx); if (Math.abs(velocityX) > 400 && (xDiff > yDiff)) { // 认为是快速的滑动 flyingScollItem(velocityX); } else { // 滑动 if (fingerDx == 0) { // 不对其进行处理,直接break break; } scrollItem(fingerDx); } return true; } return super.onTouchEvent(event); }
2.获取滑动的速率,当大于一定值认为是快速滑动否则是普通滑动,实现这几个方法:
/** * 普通滚动 * * @param fingerDx * 手指在X轴上的移动距离 */ private void scrollItem(int fingerDx) { int scrollX = getScrollX(); int cureentItem = scrollX / mIconWidth; int left = scrollX % mIconWidth; // 无论向哪一边滑动只要超过1/3就切换 if (fingerDx > 0) { // 向右边滑动 if (left > mIconWidth / 3) { cureentItem += 1; } } else { // 向左边滑动 if (left > mIconWidth * 2 / 3) { cureentItem += 1; } } scrollToItem(cureentItem); } /** * 快速滚动时的页面切换 */ private void flyingScollItem(int velocityX) { if (velocityX < 0) { // 滚动到下一张 mCureentItem += 1; scrollToItem(mCureentItem); } else { // 滚动到上一张 mCureentItem -= 1; scrollToItem(mCureentItem); } } /** * 滚动到当前条目 */ private void scrollToItem(int cureentItem) { mCureentItem = cureentItem; smoothScrollTo(mCureentItem * mIconWidth, 0); }
3.测试一把,看看效果。下面重头戏来了,HorizontalScrollView内部是不会自动销毁和重用子条目的,如果添加的内容过多肯定会造成OOM:
2.1HorizontalScrollView的OOM问题:
1.ListView的Adapter我们已经用得不能再熟了,就把的源码拿出来用用,首先新建BaseAdapter,然后用到实现中。public abstract class BaseAdapter { // 获取条目数 public abstract int getCount(); // 获取条目Id public abstract int getItemId(int position); // 获取条目 public abstract Object getItem(int position); // 获取View public abstract View getView(int position, View convertView, ViewGroup parent); } public class RevealAdapter extends BaseAdapter { private int[] mColouredResouce; public RevealAdapter(int[] colouredResouce) { this.mColouredResouce = colouredResouce; } @Override public int getCount() { return mColouredResouce.length; } @Override public View getView(int position, View convertView, ViewGroup parent) { ImageView image; if (convertView == null) { image = new ImageView(parent.getContext()); } else { image = (ImageView) convertView; } Drawable grayDrawable = parent.getContext().getResources() .getDrawable(mColouredResouce[position]); Drawable drawable = parent.getContext().getResources() .getDrawable(mColouredResouce[position]); RevealDrawable revealDrawable = new RevealDrawable( BitmapUtil.toGrayscale(grayDrawable), drawable); image.setImageDrawable(revealDrawable); return image; } @Override public int getItemId(int position) { return mColouredResouce[position]; } @Override public Object getItem(int position) { return mColouredResouce[position]; } }
2.给HorizontalScrollView设置我们自定义的Adapter,在设置adapter之后默认load第一屏数据
// 数据适配源 private BaseAdapter mAdapter; // ScrollView的宽度 private int mMeasuredWidth; // 一屏幕显示多少个 private int mScreenCount; // 第一个位置 private int mFirstPosition; // 最后一个位置 private int mLastPosition; // 位置记录 private HashMap<View, Integer> mRecordPos; /** * 设置数据适配 */ public void setAdapter(BaseAdapter adapter) { if (adapter == null) { throw new RuntimeException("adapter is null..."); } this.mAdapter = null; this.mAdapter = adapter; initAdapterData(); } /** * 初始化数据适配 */ private void initAdapterData() { mContainer.removeAllViews(); mRecordPos.clear(); // 获取第一个Item final ImageView firstView = (ImageView) mAdapter.getView(0, null, mContainer); // 第一张图片 firstView.setImageLevel(5000); mContainer.addView(firstView); mRecordPos.put(firstView, 0); mFirstPosition = 0; post(new Runnable() { @Override public void run() { // 得到item的宽度 mIconWidth = firstView.getMeasuredWidth(); loadFirstScreenItem(); } }); } /** * 加载第一屏的条目 */ private void loadFirstScreenItem() { mMeasuredWidth = getMeasuredWidth(); mCenterX = getWidth() / 2 - mIconWidth / 2; mContainer.setPadding(mCenterX, 0, mCenterX, 0); // 默认按一屏幕计算 mScreenCount = mMeasuredWidth / mIconWidth + 2; // i从1开始,之前已经加入了一个 for (int i = 1; i < mScreenCount; i++) { View view = mAdapter.getView(i, null, mContainer); mContainer.addView(view); mRecordPos.put(view, i); } mLastPosition = mScreenCount - 1; }
3.接下来只要弄清楚什么时候需要加载下一个以及移除上一个就很好办事了,其实就是我们向右滑动一定距离之后我们要把第一个移除并且加载后一个,让第一个item作为复用的item传出去,反之向左移动一段距离就是移除最后一个,加载第一个:
@Override public boolean onTouchEvent(MotionEvent event) { final int action = event.getAction(); switch (action) { case MotionEvent.ACTION_MOVE: int scrollX = getScrollX(); // 如果当前scrollX为view的宽度,加载下一张,移除第一张 if (scrollX > mIconWidth + mCenterX) { loadNextImg(); } // 如果当前scrollX = 0, 往前设置一张,移除最后一张 if (scrollX <= mCenterX) { loadPreImg(); } break; } return super.onTouchEvent(event); } /** * 加载下一张图片 */ private void loadNextImg() { if (mLastPosition >= mAdapter.getCount() - 1) { return; } // 移除第一张 View firstChild = mContainer.getChildAt(0); mContainer.removeView(firstChild); mFirstPosition += 1; mRecordPos.remove(firstChild); // 加载最后一张 mLastPosition += 1; View lastChild = mAdapter.getView(mLastPosition, firstChild, mContainer); mContainer.addView(lastChild); mRecordPos.put(lastChild, mLastPosition); // 滚动到当前位置,设置当前位置为1 scrollTo(getScrollX() - mIconWidth, 0); mCureentItem = getScrollX() / mIconWidth; } /** * 加载前一张 */ private void loadPreImg() { if (mFirstPosition == 0) { return; } // 移除掉最后一张 View lastChild = mContainer.getChildAt(mContainer.getChildCount() - 1); mContainer.removeView(lastChild); mRecordPos.remove(lastChild); mLastPosition -= 1; // 加载第一张 mFirstPosition -= 1; View firstView = mAdapter.getView(mFirstPosition, lastChild, mContainer); mContainer.addView(firstView, 0); mRecordPos.put(firstView, mFirstPosition); // 水平滚动位置向左移动view的宽度个像素 scrollTo(getScrollX() + mIconWidth, 0); mCureentItem = getScrollX() / mIconWidth; }
相关文章推荐
- Android开源图表库
- Android系统中的屏幕状态切换以及亮度设置
- Android Studio--Intent启动活动
- android 开发中常见问题合集(持续更新)
- Android Callable Future FutureTask学习
- Android6.0系统权限
- Android 屏幕保持唤醒
- Android工程师入门(二)——不忙不累怎么睡。。
- Android电源管理之一:基础概览
- 唤醒锁: 检测 Android* 应用中的 No-Sleep(无法进入睡眠)问题
- Android 自定义滑动容器View(2)
- AndroidStudio 中使用 百度地图 <二> 百度地图的使用,个人摘要
- Android查找方法所在的包快捷键
- Android LayoutInflater详解
- 在Android应用中使用SQLite数据库的方法
- Android获取SD卡上所有图片、视频缩略图和音乐专辑封面
- 【android】怎么压缩和缓存图片?告诉你正确的姿势
- 反编译android_apk
- Android 最火的快速开发框架XUtils
- android学习第二周_布局优化技巧及列表控件_学习笔记