IOS Reveal 效果

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没有重用机制,虽然可能我们并不会加载很多的图片,但是一旦过多肯定会出现内存溢出的问题;
  

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值