1. 效果
今天在IOS中看到了这种效果觉得还蛮不错的,所以自己就有点忍不住了,查一下资料撸一把代码啦。
2.分析和实现
2.1效果分析:
在网上找了资料也没找出个所以然来,就效果来看,能够想到一些不同的实现方式:
1.在滑动的过程中,获取bitmap,把bitmap分成三个部分,前半部分,中间部分,和后面部分。那么只有中间是彩色的,其余都是灰色,灰色怎么来呢?就是对彩色图片进行处理,处理完之后把这三个部分拼起来;
2 .每张图片其实可以看成有两个drawable,只要一滑动,我就去切一部分彩色的drawable和一部分灰色的drawable然后把他们组成一张drawable就可以实现这种效果了;
我们采用第二种方式来试着实现玩玩:我们自定义Drawable,用这个setLevel去不断的更新Drawable的onDraw()方法,先看看这张图片:
我打算把它分为这么几个层级 0~5000~1000,level在0或者1000是全灰,5000是全彩色,当然这个层级可以由自己决定。如果level 在0~5000或者5000~10000就各有灰色和彩色,这个时候我们就根据level用Gravity这个类去灰色drawable中裁剪一段,去彩色drawable中裁剪一段拼成一张drawable即可实现效果,下面开始:
2.2分步实现:
1.自定义RevealDrawable extends Drawable传一张灰色的drawable和一张彩色的drawable作为构造方法,重载一些方法:
public class RevealDrawable extends Drawable {
private Drawable mUnselectedDrawable, mSelectedDrawable;
private Rect mOutRect;
public RevealDrawable(Drawable unselectedDrawable, Drawable selectedDrawable) {
this.mUnselectedDrawable = unselectedDrawable;
this.mSelectedDrawable = selectedDrawable;
mOutRect = new Rect();
}
@Override
protected void onBoundsChange(Rect bounds) {
// 初始化数据--定义两个
mUnselectedDrawable.setBounds(bounds);
mSelectedDrawable.setBounds(bounds);
super.onBoundsChange(bounds);
}
@Override
public int getIntrinsicWidth() {
// 得到 Drawable 的实际宽度
return mSelectedDrawable.getIntrinsicWidth();
}
@Override
public int getIntrinsicHeight() {
// 得到 Drawable 的实际高度
return mSelectedDrawable.getIntrinsicHeight();
}
@Override
public void setAlpha(int alpha) {
}
@Override
public void setColorFilter(ColorFilter cf) {
}
@Override
public int getOpacity() {
return 0;
}
}
2.最重要的部分就在这里了,当我们在ImageView中调用setImageLevel(int level)方法时–>Drawable调用setLevel(int level)–>onLevelChange(int level),所以需要在onLevelChange()方法中调用invalidateSelf()不断的请求刷新–onDraw(),在该方法中我们去判断level的值–>Gravity.apply裁剪:
@Override
protected boolean onLevelChange(int level) {
// 感知setLevel的调用然后刷新--Draw()
invalidateSelf();
return true;
}
/**
* level:0~10000;全彩色:5000;全灰色:0 或者 10000;渐变:0~5000 5000~10000
*/
@Override
public void draw(Canvas canvas) {
int level = getLevel();
if (level == 0 || level == 10000) {
// 全灰
mUnselectedDrawable.draw(canvas);
} else if (level == 5000) {
// 全彩
mSelectedDrawable.draw(canvas);
} else {
// 渐变(一部分灰色一部分彩色)
// 得到当前drawable的矩形边界
Rect bounds = getBounds();
{
// 1.从灰色的图片抠出左边部分矩形
// level 0~5000
float ratio = (level / 5000f) - 1f;
int w = bounds.width();
w = (int) (w * Math.abs(ratio));
int h = bounds.height();
int gravity = ratio < 0 ? Gravity.LEFT : Gravity.RIGHT;
// 裁剪
Gravity.apply(gravity,// 从左边开始切还是从右边开始切
w,// 目标矩形的宽
h,// 高
bounds,// 被抠出的原矩形
mOutRect // 最终的Rect 画布里面需要的矩形区域
);
// 先保存画布原型
canvas.save();
// 将画布裁剪一部分出来
canvas.clipRect(mOutRect);
mUnselectedDrawable.draw(canvas);
// 恢复画布
canvas.restore();
}
{
// 2. 从彩色的图片抠出右边部分矩形
// level 5000~10000
float ratio = (level / 5000f) - 1f;
int w = bounds.width();
w -= (int) (w * Math.abs(ratio));
int h = bounds.height();
int gravity = ratio < 0 ? Gravity.RIGHT : Gravity.LEFT;
// 裁剪
Gravity.apply(gravity,// 从左边开始切还是从右边开始切
w,// 目标矩形的宽
h,// 高
bounds,// 被抠出的原矩形
mOutRect // 最终的Rect 画布里面需要的矩形区域
);
// 先保存画布原型
canvas.save();
// 将画布裁剪一部分出来
canvas.clipRect(mOutRect);
mSelectedDrawable.draw(canvas);
// 恢复画布
canvas.restore();
}
}
}
3.下面就可以在单张图片中测试一把,我们在MainActivity的布局中放置一张图片,我们只有一张彩色的图片资源,那么就需要用代码把它转成灰色的drawable,给图片设置成我们自定义的RevealDrawable,点击图片不断累加level值看看效果:
4.最后把这个用到ScrollView中,这里我么肯定也要自定义ScrollView了,不过相比起来就比较简单了,提供一个addImageResouceIds(int[] resouceIds)方法设置资源图片,重载onScrollChanged()方法监听滚动设置图片的level
public class RevealScrollView extends HorizontalScrollView {
// 图片容器
private LinearLayout mContainer;
// 图片宽度
private int mIconWidth;
private int mCenterX;
/**
* 初始化
*/
private void init() {
mContainer = new LinearLayout(getContext());
RevealScrollView.LayoutParams params = new RevealScrollView.LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
mContainer.setLayoutParams(params);
mContainer.setOrientation(LinearLayout.HORIZONTAL);
}
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
reveal(l);
}
/**
* 控制渐变
*/
private void reveal(int scrollX) {
// 滑动距离--->setLevel
int indexLeft = scrollX / mIconWidth;
LogUtils.e("当前是第(" + indexLeft + ")张");
int indexRight = indexLeft + 1;
int childCount = mContainer.getChildCount();
for (int i = 0; i < childCount; i++) {
if (i == indexLeft || i == indexRight) {
// 属于渐变
ImageView leftIv = (ImageView) mContainer.getChildAt(indexLeft);
// ratio = 5000/mIconWidth = level/彩色部分的长度
float ratio = (float) 5000 / mIconWidth; // 把5000转为float
// ,不然integer/integer会取整会出现细微的差别
if (leftIv != null) {
// float level = ratio * 彩色部分的长度 = ratio *
// (mIconWidth-scrollX%mIconWidth)
float level = ratio * (mIconWidth - scrollX % mIconWidth);
leftIv.setImageLevel((int) level);
}
ImageView rightIv = (ImageView) mContainer
.getChildAt(indexRight);
if (rightIv != null) {
rightIv.setImageLevel((int) (10000 - scrollX % mIconWidth
* ratio));
}
} else {
// 全灰色 0或者10000
ImageView iv = (ImageView) mContainer.getChildAt(i);
iv.setImageLevel(0);
}
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
View view = mContainer.getChildAt(0);
mIconWidth = view.getWidth();
mCenterX = getWidth() / 2 - mIconWidth / 2;
mContainer.setPadding(mCenterX, 0, mCenterX, 0);
}
/**
* 添加图片资源
**/
public void addImageResouceIds(int[] resouceIds) {
for (int i = 0; i < resouceIds.length; i++) {
Drawable drawable = new RevealDrawable(BitmapUtil.toGrayscale(
getContext(), resouceIds[i]), getResources().getDrawable(
resouceIds[i]));
ImageView iv = new ImageView(getContext());
iv.setImageDrawable(drawable);
if (i == 0) {
// 设置选中全彩色
iv.setImageLevel(5000);
}
mContainer.addView(iv);
}
addView(mContainer);
}
}
4.最后还遗留着几个问题 (明天解决一下):
(1)如果把它做成ViewPager效果的方式可能会更好;
(2)HorizontalScrollView没有重用机制,虽然可能我们并不会加载很多的图片,但是一旦过多肯定会出现内存溢出的问题;