实现猫眼电影影片浏览效果

前言部分

因为项目要对接猫眼电影,需要做一个和猫眼电影一样的影片选择效果,其实这本篇文章的内容是网上凑出来的。虽说是东拼西凑,但是过程中还是有些收获,毕竟有些东西还是要亲自实践才能深入理解。

不多说先上个效果图吧:

在这里插入图片描述

内容部分

实现效果大体分为几部分:

  1. 一个横向滚动的列表(RecyclerView)
  2. 列表要选中的条目需要居中显示(LinearSnapHelper)
  3. 选中的条目需要放大效果(或者把未选中的缩小)
  4. 添加一个背景高斯模糊,这个网上有标准写法,直接拿来使用,未考虑性能等问题。
  5. 细节上就是打开页面播放了一个动画,让item有一个类似呼吸的效果。
下面正式开始实现
  1. 首先一个横向的RecyclerView列表先实现出来。这个很常规就不贴代码了。

  2. 让条目居中这里我们使用到一个辅助的类LinearSnapHelper,这个类其实很早就有了,但是并没有很大的被使用,我也是上次用RecyclerView实现viewpager效果的时候使用过PagerSnapHelper。代码并不需要我们做特别的处理,主要调用attachToRecyclerView方法传入RecyclerView即可。

  3. 条目的放大特效实现,这里我自己写了一个,但是至少单纯调用view的setScaleX方法,效果看起来比较僵硬,后来又使用动画实现了一版改良效果。但始终还是不尽如人意,后来就网上搜索了一个方案,通过判断选中的view距离中心点的距离进行缩放。代码如下:

     @Override
        public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) {
            int childCount = mRecyclerView.getChildCount();
    //        Log.e("scrollHorizontallyBy", dx + "");
            int[] location = new int[2];
            for (int i = 0; i < childCount; i++) {
                View v = mRecyclerView.getChildAt(i);
                v.getLocationOnScreen(location);
                int recyclerViewCenterX = mRecyclerView.getLeft() + mRecyclerView.getWidth() / 2;
                int itemCenterX = location[0] + v.getWidth() / 2;
    //          ★ 两边的图片缩放比例
                float scale = 0.8f;
    //           ★某个item中心X坐标距recyclerview中心X坐标的偏移量
                int offX = Math.abs(itemCenterX - recyclerViewCenterX);
    //          ★ 在一个item的宽度范围内,item从1缩放至scale,那么改变了(1-scale),
    //              从下列公式算出随着offX变化,item的变化缩放百分比
                float percent = offX * (1 - scale) / v.getWidth();
    //                   ★  取反哟
                float interpretateScale = 1 - percent;
    //                    这个if不走的话,得到的是多级渐变模式
                if (interpretateScale < scale) {
                    interpretateScale = scale;
                }
                v.setScaleX((interpretateScale));
                v.setScaleY((interpretateScale));
            }
            return super.scrollHorizontallyBy(dx, recycler, state);
    
        }
    
  4. 高斯模糊的实现也是网上摘了一段代码,这里需要注意的可能是你需要通过每个条目上的图片来做模糊,所以生成的模糊后的图片尺寸要设置尽可能小,因为bitmap的操作是很消耗内存的,所以要满足效果的同时尽可能减少内存的消耗。代码如下:

    public void onScrollStateChanged(int state) {
            switch (state) {
                case RecyclerView.SCROLL_STATE_IDLE:
                    View snapView = linearSnapHelper.findSnapView(this);
                    ImageView image = snapView.findViewById(R.id.image);
                    Drawable background = image.getDrawable();
                    mRecyclerView.setBackground(creatBitmap(Utils.drawableToBitmap(background)));
                    currentPosition = getPosition(snapView);
                    if (positionSelectListener != null) {
                        if (oldPosition != currentPosition) {
                            positionSelectListener.selectItem(currentPosition);
                            oldPosition = currentPosition;
                        }
                    }
                    break;
                case RecyclerView.SCROLL_STATE_DRAGGING:
                    break;
                case RecyclerView.SCROLL_STATE_SETTLING:
                    break;
                default:
                    break;
    
            }
    
        }
    

    上面主要过程是,通过linearSnapHelper.findSnapView(this)来获取中心的view,然后在view中获取到需要做模糊处理的image,然后在依次进行转换Drawable—>Bitmap—>转换完成的bitmap—>Drawable—>设置给mRecyclerView的背景。

    下面类似的是一个工具方法了,详细的内容不在多说了,因为我也不了解。哈哈

     private Drawable creatBitmap(Bitmap bitmap) {
            //创建一个缩小后的bitmap
            Bitmap inputBitmap = Bitmap.createScaledBitmap(bitmap, 10, 10, false);
            //创建将在ondraw中使用到的经过模糊处理后的bitmap
            Bitmap outputBitmap = Bitmap.createBitmap(inputBitmap);
            //创建RenderScript,ScriptIntrinsicBlur固定写法
            RenderScript rs = RenderScript.create(context);
            ScriptIntrinsicBlur blurScript = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));
            //根据inputBitmap,outputBitmap分别分配内存
            Allocation tmpIn = Allocation.createFromBitmap(rs, inputBitmap);
            Allocation tmpOut = Allocation.createFromBitmap(rs, outputBitmap);
            //设置模糊半径取值0-25之间,不同半径得到的模糊效果不同
            blurScript.setRadius(15);
            blurScript.setInput(tmpIn);
            blurScript.forEach(tmpOut);
    
            //得到最终的模糊bitmap
            tmpOut.copyTo(outputBitmap);
            return new BitmapDrawable(outputBitmap);
    
        }
    
  5. 初始化的动画我加在了onLayoutCompleted方法中并且做了一些限制,因为该方法在一些情况下要被多次调用。下面是代码:

    if (isFirstCompleted) {
                isFirstCompleted = false;
                childCount = mRecyclerView.getChildCount();
                mRecyclerView.setEnabled(false);
                for (int i = 0; i < childCount; i++) {
                    View v = mRecyclerView.getChildAt(i);
                    animatorSet = new AnimatorSet();
                    ObjectAnimator scaleX = ObjectAnimator.ofFloat(v, "scaleX", 1.0f, 1.1f, 0.9f, 0.8f);
                    ObjectAnimator scaleY = ObjectAnimator.ofFloat(v, "scaleY", 1.0f, 1.1f, 0.9f, 0.8f);
                    animatorSet.setDuration(1000);
                    animatorSet.setInterpolator(new LinearInterpolator());
                    animatorSet.play(scaleX).with(scaleY);
                    animatorSet.start();
    
                }
                animatorSet.addListener(new SampleAnimatorListenerImpl() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        //初始化完成,first上的position放大
                        View v = mRecyclerView.getChildAt(0);
                        AnimatorSet animatorSet = new AnimatorSet();
                        ObjectAnimator scaleX = ObjectAnimator.ofFloat(v, "scaleX", 0.8f, 1.0f);
                        ObjectAnimator scaleY = ObjectAnimator.ofFloat(v, "scaleY", 0.8f, 1.0f);
                        animatorSet.setDuration(300);
                        animatorSet.setInterpolator(new LinearInterpolator());
                        animatorSet.play(scaleX).with(scaleY);
                        animatorSet.start();
    
                        ImageView image = v.findViewById(R.id.image);
                        Drawable background = image.getDrawable();
                        mRecyclerView.setBackground(creatBitmap(Utils.drawableToBitmap(background)));
                        canScroll = true;
                    }
    
                });
            }
    

    上面主要是两个部分,一部分是给整体显示的item设置一个呼吸的动画效果;一部分是在动画完成后给第一个选中的item设置一个放大的效果和一个背景模糊的效果。

  6. 处理一下调用mRecyclerview.smoothScrollToPosition(position);方法的逻辑,源码显示调用该方法后会调用到layoutManager中的smoothScrollToPosition()方法,在这我们要处理一下中心对齐。代码如下:

    CenterSmoothScroller centerSmoothScroller = new CenterSmoothScroller(context);
            centerSmoothScroller.setTargetPosition(position);
            startSmoothScroll(centerSmoothScroller);
    //CenterSmoothScroller类中主要就是方法如下,其中对位置进行了修正。这里没读源码有兴趣可以了解一下LinearSmoothScroller类。
     @Override
        public int calculateDtToFit(int viewStart, int viewEnd, int boxStart, int boxEnd, int snapPreference) {
            return boxStart + (boxEnd - boxStart) / 2 - (viewStart + (viewEnd - viewStart) / 2);
        }
    
  7. 添加一个回调方法告诉调用者当前是选中的哪个position,调用者对应更新影片详情。这里就不具体说了,比较简单。

结束部分

其实上面的内容并没有我自己写的东西,不过是把网上的东西东拼西凑出来的,但是在拼凑前确实也仔细思考实现,只是效果不如人意,其实其他东西也是可以先自己来思考处理实现,然后在去看轮子如何实现,从中找到差异来不断精进,毕竟不是每个人都是大神。

有问题欢迎纠正,谢谢啦

如果对你有帮助就点个赞把

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值