您的位置:首页 > 移动开发 > Android开发

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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: