android使用属性动画--高仿B站频道详情页头部动效

先上效果图

B站的效果
demo效果

分析

1.点击按钮时列表向下移动的动画
2.点击按钮背景图片放大并且渐出
3.点击按钮横向滑动列表拉伸渐出
4.滑动列表标题栏渐变色的效果

解析

1.使用属性动画 ValueAnimator动态改变横向滑动列表的高度来实现往下移动的效果
2.监听向下移动的动画,根据动画执行过程计算图片的透明度和列表宽高实现渐出的效果
3.监听appbar的滑动,动态改变标题的透明度

附上一个万能的公式,根据偏移量计算值:大值-(大值-小值)(当前偏移量/最大的偏移量的范围)

public float getSize(int max, int min, float absOffset, float totalScrollRange) {
        return max - ((max - min) * (absOffset / totalScrollRange));
    }

布局文件

使用自定义组合控件


<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/top_view_root"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    tools:context="com.vivo.video.online.shortvideo.topics.AllTopicsActivity">

    <ImageView
        android:id="@+id/bg_image"
        android:layout_width="match_parent"
        android:layout_height="175dp"
        android:scaleType="centerCrop"
        android:src="@drawable/bili"
        app:layout_scrollFlags="scroll" />

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="16dp"
        android:layout_marginTop="100dp"
        android:orientation="vertical">

        <ImageView
            android:id="@+id/switch_view"
            android:padding="10dp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@+id/topics_tv_data"
            android:layout_alignParentRight="true"
            android:src="@drawable/expand_im" />

        <LinearLayout
            android:id="@+id/cc"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_below="@+id/switch_view"
            android:orientation="vertical">


            <com.vivo.video.online.widget.SuperHorizontalScrollView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="10dp">

                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:orientation="horizontal">

                    <View
                        android:layout_width="100dp"
                        android:layout_height="120dp"
                        android:background="#854CAF50" />

                    <View
                        android:layout_width="100dp"
                        android:layout_height="120dp"
                        android:layout_marginLeft="16dp"
                        android:background="#778BC34A" />

                    <View
                        android:layout_width="100dp"
                        android:layout_height="120dp"
                        android:layout_marginLeft="16dp"
                        android:background="#80CDDC39" />

                    <View
                        android:layout_width="100dp"
                        android:layout_height="120dp"
                        android:layout_marginLeft="16dp"
                        android:background="#7E009688" />

                </LinearLayout>

            </com.vivo.video.online.widget.SuperHorizontalScrollView>


        </LinearLayout>


        <TextView
            android:id="@+id/topics_tv_data"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerHorizontal="true"
            android:text="17万订阅 | 489.5万播放量 | 3万个精选视频"
            android:textColor="@color/lib_white" />


    </RelativeLayout>


</RelativeLayout>

自定义组合控件java代码--注释的很全面

public class TopicsDetailsTopView extends RelativeLayout {

    private final int ANIMATION_DURATION = 300;//动画持续的时间
    private ValueAnimator mValueAnimation;//上下平移的动画
    private boolean isAnimationExecuting = false;//动画是否正在执行
    private boolean mIsShow = false;//是否展开了
    private ImageView mSwitchView;
    private View mScrollView;//横向的列表
    private int mScrollViewHeight;//底部的横向滑动控件的高度
    private int mBgHeight;//背景的高度
    private ImageView mBgImageView;//背景图片
    private RelativeLayout mRootView;//根布局
    public float mBgImageAlpha;//背景的透明度
    private TextView mTopicsTv;

    public TopicsDetailsTopView(Context context) {
        super(context);
        initView(context);
    }

    public TopicsDetailsTopView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView(context);
    }

    public TopicsDetailsTopView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView(context);
    }

    private void initView(Context context) {
        View inflate = View.inflate(context, R.layout.topics_details_top_view, this);
        mTopicsTv = inflate.findViewById(R.id.topics_tv_data);
        mRootView = inflate.findViewById(R.id.top_view_root);
        mSwitchView = inflate.findViewById(R.id.switch_view);
        mBgImageView = inflate.findViewById(R.id.bg_image);
        mScrollView = inflate.findViewById(R.id.cc);
        getViewHeight();
        //动画执行完才可点击
        mSwitchView.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                if (isAnimationExecuting) {
                    return;
                }
                startLayoutAnimator();
            }
        });
    }

    //获取view的高度
    private void getViewHeight() {
        if (mScrollView == null || mBgImageView == null) {
            return;
        }
        ViewTreeObserver vto = mScrollView.getViewTreeObserver();
        vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                mScrollView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
                mScrollViewHeight = mScrollView.getHeight();
                ViewGroup.LayoutParams layoutParams = mScrollView.getLayoutParams();
                layoutParams.height = 0;
                mScrollView.setLayoutParams(layoutParams);
            }
        });

        ViewTreeObserver mPicVto = mBgImageView.getViewTreeObserver();
        mPicVto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                mBgImageView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
                mBgHeight = mBgImageView.getHeight();
                ViewGroup.LayoutParams layoutParams = mBgImageView.getLayoutParams();
                mBgImageView.setLayoutParams(layoutParams);
            }
        });

    }

    /**
     * 开启动画
     */
    private void startLayoutAnimator() {
        if (mScrollView == null || mSwitchView == null || mBgImageView == null) {
            return;
        }
        //设置按钮的旋转动画
        ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(mSwitchView, "rotation", mIsShow ? 0f : 180f);
        objectAnimator.setDuration(ANIMATION_DURATION);
        objectAnimator.start();
        //动态设置高度的属性动画
        mValueAnimation = mIsShow ? ValueAnimator.ofInt(mScrollViewHeight, 0) : ValueAnimator.ofInt(0, mScrollViewHeight);
        mValueAnimation.setDuration(ANIMATION_DURATION);
        mValueAnimation.start();

        mValueAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                //获取当前的height值
                int currentHeight = (int) valueAnimator.getAnimatedValue();

                //动态更新view的高度
                ViewGroup.LayoutParams layoutParams = mScrollView.getLayoutParams();
                layoutParams.height = Math.round(currentHeight);
                mScrollView.setLayoutParams(layoutParams);

                //设置背景的高度--逐渐放大的效果
                ViewGroup.LayoutParams mPicParams = mBgImageView.getLayoutParams();
                mPicParams.height = Math.round((currentHeight + mBgHeight));
                mBgImageView.setLayoutParams(mPicParams);

                //设置背景和滑动列表的透明度渐出渐入的效果
                float maxAlpha = mBgImageAlpha;
                float minAlpha = 0.0f;
                float height = currentHeight;
                float maxHeight = mScrollViewHeight;
                float offSetAlpha = (maxAlpha - ((maxAlpha - minAlpha) * (height / maxHeight)));
                float bgAlpha = Float.parseFloat(String.format("%.2f", offSetAlpha));
                setBgImageAlpha(bgAlpha);//背景逐渐退出
                mScrollView.setAlpha(1 - bgAlpha);//滑动控件逐渐出来
                isAnimationExecuting = true;
                mTopicsTv.setText(bgAlpha+"");
            }
        });
        mValueAnimation.addListener(new DefaultAnimatorListener() {
            @Override
            public void onAnimationEnd(Animator animation, boolean isReverse) {
                mIsShow = !mIsShow;
                isAnimationExecuting = false;
            }
        });
    }

    /**
     * 根view
     */
    public RelativeLayout getTopRootView() {
        return mRootView;
    }

    /**
     * 图片
     */
    public ImageView getBgImageView() {
        return mBgImageView;
    }

    //设置背景透明度
    public void setBgImageAlpha(float mBgImageAlpha) {
        if (mBgImageView == null) {
            return;
        }
        mBgImageView.setAlpha(mBgImageAlpha);
    }

    /**
     * 是否展开了
     */
    public boolean getIsShow() {
        return mIsShow;
    }

    /**
     * 是否正在执行动画
     */
    public boolean getAnimationExecuting() {
        return isAnimationExecuting;
    }

}


外层的列表用到CoordinatorLayout

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:animateLayoutChanges="true">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/app_bar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
        app:elevation="0dp">

        <com.vivo.video.online.shortvideo.topics.view.TopicsDetailsTopView
            android:id="@+id/topics_details_top_view"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layout_scrollFlags="scroll" />

    </android.support.design.widget.AppBarLayout>

    <com.vivo.video.online.widget.recyclerview.OnlineVideoRecyclerView
        android:id="@+id/list_recycle"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior" />


    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="80dp"
        android:gravity="center">

        <View
            android:id="@+id/title_root_bg"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

        <!--  标题的背景颜色-->
        <ImageView
            android:id="@+id/back_btn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:layout_marginBottom="20dp"
            android:contentDescription="@string/talk_back_back"
            android:paddingStart="16dp"
            android:src="@drawable/lib_back_button_white_arrow" />

        <ImageView
            android:id="@+id/share_btn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentRight="true"
            android:layout_alignParentBottom="true"
            android:layout_marginEnd="20dp"
            android:layout_marginBottom="20dp"
            android:contentDescription="@string/talk_back_more_menu"
            android:src="@drawable/share_white" />


        <TextView
            android:id="@+id/tv_title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:layout_centerHorizontal="true"
            android:layout_marginStart="16dp"
            android:layout_marginTop="10dp"
            android:layout_marginBottom="10dp"
            android:textColor="@color/lib_white"
            android:textSize="16dp"
            tools:text="这就是艺术" />


    </RelativeLayout>

</android.support.design.widget.CoordinatorLayout>

activity里面的主要的代码

  //滑动值变化的的最大范围
  private final float SCROLL_RANGE = ResourceUtils.dp2px(50);
  private final float DEFAULT_BG_ALPHA = 0.7f; //默认的背景图片透明度

    TextView mTitleView = findViewById(R.id.tv_title);//标题
    View mTitleRootView = findViewById(R.id.title_root_bg);//标题的背景
    AppBarLayout  mAppBarLayout = findViewById(R.id.app_bar);
    TopicsDetailsTopView mTopView = findViewById(R.id.topics_details_top_view);
    ImageView mBgImageView = mTopView.getBgImageView();//背景图片
    RelativeLayout mTopRootView = mTopView.getTopRootView();//标题的根布局

 if (mTitleView == null || mAppBarLayout == null || mTitleRootView == null) {
            return;
        }

//设置头部背景颜色
 mTitleRootView.setBackgroundColor(Color.parseColor("#D6B675"));
 mTopRootView.setBackgroundColor(Color.parseColor("#D6B675"));

        mAppBarOffsetChangedListener = new AppBarStatesChangeListener() {
            @Override
            public void onStateChanged(AppBarLayout appBarLayout, State state, int verticalOffset) {
                float absOffset = Math.abs(verticalOffset);
                //标题的渐变
                float titleAlpha = getBgAlpha(1, 0, absOffset, SCROLL_RANGE);
                mTitleRootView.setAlpha(1 - titleAlpha);

                //背景的渐变
                float bgAlpha = getBgAlpha(DEFAULT_BG_ALPHA, 0, absOffset, SCROLL_RANGE);
                //不展开才设置背景的渐出
                if (!mTopView.getIsShow() && !mTopView.getAnimationExecuting()) {
                    mTopView.setBgImageAlpha(bgAlpha);
                }
                mTopView.mBgImageAlpha = bgAlpha;
                mTitleView.setText(bgAlpha + "");
            }
        };
        mAppBarLayout.addOnOffsetChangedListener(mAppBarOffsetChangedListener);
/**
 * 根据偏移量获取透明度
 */
private float getBgAlpha(float maxAlpha, float minAlpha, float absOffset, float scrollRange) {
    float bgAlphaOffset = maxAlpha - ((maxAlpha - minAlpha) * (absOffset / scrollRange));
    if (bgAlphaOffset <= 0) {
        bgAlphaOffset = 0;
    }
    return Float.parseFloat(String.format("%.2f", bgAlphaOffset));
}

不懂的小伙伴请在评论区提问,欢迎大家下载demo体验>_<!!!

demo.png

点击链接下载apk:https://wwa.lanzous.com/iXxsjgf73ta

二维码下载

扫描二维码下载
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,047评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,807评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,501评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,839评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,951评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,117评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,188评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,929评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,372评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,679评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,837评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,536评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,168评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,886评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,129评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,665评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,739评论 2 351