仿网易云音乐日推界面(监听AppBarLayout滑动+动态高斯模糊)

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/rikkatheworld/article/details/98182808

首先来看下日推界面的效果:
在这里插入图片描述

网易云音乐日推界面的亮点就是在上拉的时候,banner页面逐渐模糊。字体透明度下降,最后左上角显示出“每日推荐”的字体。
这个界面用户会觉得很清晰、便捷又不失美观(网易云音乐的AppUI设计是真的太顶)
但是去模仿去做,其实又会遇到一些坑。
比如说,这里明显使用了AppBarLayout,图片放在CollapsingToolbarLayout里,但是:下面的RecyclerView的有上圆角,仿佛就是View在Recycler下层一样,如果用AppBarLayout的话,图片是不可能覆盖到下面的Recycler的。
更别说RecycerView还有两个类似标签的小View。

我这里想到了两种做法:

  1. 整个界面自定义成一个ViewGroup,将图片、RecycleView封装,对于上滑RecyclerView做手势监听,如果被RecyclerView包裹的RelativeLayout没有到达顶部则不滑动RecyclerView BALABALBALBALALBAL。。。。
    怎么说呢,我认为网易云音乐他们就是这么实现的,只要开一下开发者选项的 布局边界显示就能发现他们处理的很巧妙
  2. 还是用AppBarLayout实现,首先AppBarLayout是可以监听滑动的,所以便于我们做动态高斯模糊,其次,RecyclerView的上圆角没有,但是我们也是可以自己构造呀!

这里我选择第二种方法实现。低仿后的效果如下:
在这里插入图片描述
圆角、动态高斯模糊、字体透明度都实现了~

需要学习什么

只要学习两个就够了

  1. 关于CoordinatorLayoutAppBarLayout的使用
    我之前写过一篇Blog就是讲这个的。讲的不是很细,但是把用法和注意的点都讲解到了。其实只要学会了Behavior也够我们开发产品了。
    小学CoordinatorLayout的交互

  2. 动态高斯模糊
    学习了Android开发学习之路-动态高斯模糊怎么做
    这个大神用了双ImageView重叠来做,(其实我一开始也想到了用重叠,但是我不知道ImageView.setImageAlpha(alpha)这个方法= =!)

  3. 沉侵式状态栏
    这个emmmmm,没什么好说的吧,v21之后都好设置,网上blog讲的也很多,这里只要设置activity的theme:
    差不多就行了↓

 <style name="TranslucentTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <item name="android:windowTranslucentStatus">true</item>
        <item name="android:windowTranslucentNavigation">true</item>
    </style>

并且需要在xml文件中把CoordinatorLayout的 android:fitsSystemWindows=""不用写

我这边阐述一下动态高斯模糊的思路。

动态高斯模糊思路

首先高斯模糊的做法,主流的有两种:

1、Android自带的Renderscript
使用Android自带的API,首先要在 app.gradle的defaultConfig写入:

        renderscriptTargetApi 26(这里是minSdk)
        renderscriptSupportModeEnabled true

然后通过下面的方法,传入原bitmap和模糊半径,来获取一个模糊后的bitmap:

    private Bitmap blur(Bitmap bitmap, float radius) {
        Bitmap output = Bitmap.createBitmap(bitmap); // 创建输出图片
        RenderScript rs = RenderScript.create(this); // 构建一个RenderScript对象
        ScriptIntrinsicBlur gaussianBlue = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs)); // 创建高斯模糊脚本
        Allocation allIn = Allocation.createFromBitmap(rs, bitmap); // 创建用于输入的脚本类型
        Allocation allOut = Allocation.createFromBitmap(rs, output); // 创建用于输出的脚本类型
        gaussianBlue.setRadius(radius); // 设置模糊半径,范围0f<radius<=25f
        gaussianBlue.setInput(allIn); // 设置输入脚本类型
        gaussianBlue.forEach(allOut); // 执行高斯模糊算法,并将结果填入输出脚本类型中
        allOut.copyTo(output); // 将输出内存编码为Bitmap,图片大小必须注意
        rs.destroy(); // 关闭RenderScript对象,API>=23则使用rs.releaseAllContexts()
        return output;
    }

效果就不展示了,反正是真的可以用。效率高。
2、Glide自带的
想不到吧?
只要在导入:

    api 'com.github.bumptech.glide:glide:4.9.0'
    annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0'
    api 'jp.wasabeef:glide-transformations:4.0.1' //高斯模糊api

然后在代码中引入:

             Glide.with(this)
                  .load(coverUrl)
                  //第一个参数时模糊半径,范围在 0<radius<=25f,第二个参数是放大参数,默认是1
                  .apply(RequestOptions.bitmapTransform(new BlurTransformation(25, 1)))
                  .into(ivBg);

思路
上面的Api其实并不是最重要的,不要以为用了这些方法,就能在监听滑动的时候去用这些方法就可以达成效果了。
这是因为 你每次滑动,就要进行这些计算,如果图片精度、分辨率很高,一次计算达到了上百ms,那我们一次滑动至少产生15次以上的计算,这必然会产生卡顿。

所以我们要做的动态模糊,其实是一个幌子,我们不是根据监听滑动进行模糊,而是让视觉效果让它变得模糊,就像是欺骗的感觉

√:我们拿一份已经模糊的图放在低层,再拿一张完全不模糊的放在上层覆盖它,然后随着滑动,上层的透明度逐渐减低,下层逐渐显现出来,这样的视觉效果,正是一个图片逐渐模糊。
设置图片透明度使用 setImageView(alpha),呜呜呜我之前不知道这个方法,结果一直找不到解决方法。

实现

1、监听AppBarLayout滑动
因为AppBarLayou自带的api不太方便,我们需要再封装一层:

/**
 * AppBarLayout的监听类
 */
public abstract class AppBarStateChangeListener implements AppBarLayout.OnOffsetChangedListener {
    private static final String TAG = "AppBarStateChangeListen";

    public enum State {
        EXPANDED,
        COLLAPSED,
        IDLE
    }

    private State mCurrentState = State.IDLE;

    @Override
    public void onOffsetChanged(AppBarLayout appBarLayout, int i) {
        onOffsetChanged(appBarLayout);
        if (i == 0) {
            if (mCurrentState != State.EXPANDED) {
                onStateChanged(appBarLayout, State.EXPANDED);
            }
            mCurrentState = State.EXPANDED;
        } else if (Math.abs(i) >= appBarLayout.getTotalScrollRange()) {
            if (mCurrentState != State.COLLAPSED) {
                onStateChanged(appBarLayout, State.COLLAPSED);
            }
            mCurrentState = State.COLLAPSED;
        } else {
            if (mCurrentState != State.IDLE) {
                onStateChanged(appBarLayout, State.IDLE);
            }
            mCurrentState = State.IDLE;
        }
    }

    //状态发生了改变
    public abstract void onStateChanged(AppBarLayout appBarLayout, State state);

    //发生了偏移
    public abstract void onOffsetChanged(AppBarLayout appBarLayout);
}

2、XML文件
说来你们可能不信,我对圆角的处理是这样的:
在这里插入图片描述
蓝色和天蓝色都是属于AppBarLayout,上滑需要对圆角layout设置layout_collapseMode="parallax"
然后 AppBarLayout最底层仿两个ImageView,模糊的在下面,清晰的在上面。
并且 AppBarLayout的 app:elevation 要设置为 0dp,不然在折叠的时候会产生阴影,影响白色圆角的发挥
而且注意 是 app的elevation而不是 android的 elevation

<android.support.design.widget.AppBarLayout
        android:id="@+id/appbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:elevation="0dp">

        <android.support.design.widget.CollapsingToolbarLayout
            android:layout_width="match_parent"
            android:layout_height="@dimen/dp_200"
            android:fitsSystemWindows="true"
            app:layout_scrollFlags="scroll|exitUntilCollapsed">

            //低层模糊图片
            <ImageView
                android:id="@+id/iv_background"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:scaleType="centerCrop"
                app:layout_collapseMode="parallax" />
            //上层做透明度变化的图片
            <ImageView
                android:id="@+id/iv_background_cover"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:scaleType="centerCrop"
                app:layout_collapseMode="parallax" />

            //一些字体
            <RelativeLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                app:layout_collapseMode="parallax">

                <TextView
                    android:id="@+id/tv_day"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"/>

                <TextView
                    android:id="@+id/tv_month"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content" />
            </RelativeLayout>

            //标题头
            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                style="@style/ClubToolbar"
                android:layout_width="match_parent"
                android:layout_height="@dimen/dp_55"
                android:layout_marginTop="@dimen/dp_30"
                app:layout_collapseMode="pin">

                <include
                    android:id="@+id/title"
                    layout="@layout/common_title" />
            </android.support.v7.widget.Toolbar>

            //哈哈哈哈哈这个就是一个圆角矩形
            <RelativeLayout
                android:layout_width="match_parent"
                android:layout_height="@dimen/dp_10"
                android:layout_marginTop="@dimen/dp_290"
                android:background="@drawable/bg_dailyrecommend"
                app:layout_collapseMode="pin" />
        </android.support.design.widget.CollapsingToolbarLayout>
    </android.support.design.widget.AppBarLayout>
  <RelativeLayout
        android:id="@+id/rl_play"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#ffffff"
        android:paddingBottom="@dimen/dp_40"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">
        
        <android.support.v7.widget.RecyclerView
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>
    </RelativeLayout>

3、根据监听AppBarLayout来改变透明度
AppBarLayout的滑动监听是没有 偏移值 offsetX和offsetY的,所以滑动到一定距离,字体的透明度到一定值怎么弄呢,我这里借助的是包裹着RecyclerView的RelativeLayout距离顶部的距离做判断
该layout的 getTop() 越接近顶部,说明AppBarLayout快要折叠,我根据这个值去计算。
在这里插入图片描述
从上图可以知道 我们处理getTop()返回在 Toobar高度~AppBarLayout高度.
如果在Toolbar设置了marginTop值,则也要在Toolbar高度里加上,我这里就设置了(因为使用了沉侵状态栏)。

首先在onCreate中初始化这些值:

//计算 getTop移动范围,和初始化图片
protected void initData() {  
        if (coverUrl != null) {
            Glide.with(this)
                    .load(coverUrl)
                    .into(ivBgCover);
            Glide.with(this)
                    .load(coverUrl)
                    .apply(RequestOptions.bitmapTransform(new BlurTransformation(25, 1)))
                    .into(ivBg);
        }
        //距离最小值
        minDistance = DensityUtil.dp2px(DailyRecommendActivity.this, 85);
        //距离范围差值
        deltaDistance = DensityUtil.dp2px(DailyRecommendActivity.this, 200) - minDistance;
}        

然后对appBarLayout进行监听:

 appBar.addOnOffsetChangedListener(new AppBarStateChangeListener() {
            @Override
            public void onStateChanged(AppBarLayout appBarLayout, AppBarStateChangeListener.State state) {
                if (state == State.COLLAPSED) {
                    //下面的方法会出现监听不到的情况,所以这里变成折叠状态,最好再设置一次透明度
                    setLeftTitleAlpha(255f);
                }
            }

            @Override
            public void onOffsetChanged(AppBarLayout appBarLayout) {
                //获取滑动进度
                float alphaPercent = (float) (rlPlay.getTop() - minDistance) / (float) deltaDistance;
                int alpha = (int) (alphaPercent * 255);
                LogUtil.d(TAG, "alpha : " + alpha);
                ivBgCover.setImageAlpha(alpha);
                tvMonth.setAlpha(alphaPercent);
                tvDay.setAlpha(alphaPercent);
                if (alphaPercent < 0.2f) {
                    float leftTitleAlpha = (1.0f - alphaPercent / 0.2f);
                    setLeftTitleAlpha(leftTitleAlpha);
                } else {
                    setLeftTitleAlpha(0);
                }
            }
        });

setLeftTitleAlpha就是设置左上角“每日推荐”的透明度,在网上 滑动最后 20%进度的时候,让他慢慢浮现出来。
到这里基本就已经实现了仿日推界面了。

小结

这个界面加上之前做的一个 让View一般透明的那个View算是已经完成网易云的日推模块了。

这篇blog主要是让自己 对 AppBarLayout学以致用,然后在上面加一些体用更棒的东西。
比如说 动态高斯模糊沉侵式状态栏
有些东西看起来不难,最后去实现的时候也不是很难,但是 去思考实现方案、去动手做的过程才是最能令人成长的地方。
所以多思考,看到喜欢的界面,去想一想,做一做。做个低配、中配版的出来~

猜你喜欢

转载自blog.csdn.net/rikkatheworld/article/details/98182808