【Android】Jetpack全组件实战开发短视频应用App(十七)

前言

这一篇开始我们编写帖子的详情页,帖子的类型有三种:图片,视频和文本,对应的样式也不相同,我们看下他们具体的样式

首先看下图片类型的:
在这里插入图片描述
在这里插入图片描述

第二张图片是我们往上滑动的时候显示的样式,顶部的标题会变成用户信息

我们看下视频的详情页:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
第一张是视频的宽度大于高度的时候展示的样式,这个时候上面的视频是不动的,只能滑动中间的列表
第二张是视频的宽度小于高度的时候展示的样式,进来默认是全屏展示,用户信息布局和底部的布局背景都是透明
第三张是在第二张滑动的时候展示的样式,往上滑动的时候整个视频是主键缩小的,用户信息布局和底部布局背景变成白色

然后看下文本类型的详情页
在这里插入图片描述
这个就是固定的,中间是评论列表可滑动,基本上和图片的一样

分析

我们分析下这三种详情页,发现有两部分样式是通用的,用户信息和底部布局,这两部分布局我们独立出来,需要的地方我们使用include方式加载进来
我们先写用户信息布局,我们需要预留出几个属性,左边距,背景文本颜色
在这里插入图片描述

<?xml version="1.0" encoding="utf-8"?>
<layout 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">

    <data>

        <variable
            name="feed"
            type="com.mooc.ppjoke.model.Feed" />

        <variable
            name="leftMargin"
            type="java.lang.Integer" />

        <variable
            name="fullscreen"
            type="java.lang.Boolean" />

        <import type="com.mooc.ppjoke.utils.TimeUtils" />
        
        <variable
            name="owner"
            type="androidx.lifecycle.LifecycleOwner" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/author_info"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/transparent"
        android:orientation="vertical"
        android:paddingLeft="@{leftMargin}"
        android:paddingTop="@dimen/dp_3"
        android:paddingBottom="@dimen/dp_3">


        <com.mooc.libcommon.view.PPImageView
            android:id="@+id/author_avatar"
            android:layout_width="@dimen/dp_40"
            android:layout_height="@dimen/dp_40"
            android:layout_marginTop="@dimen/dp_1"
            app:image_url="@{feed.author.avatar}"
            app:isCircle="@{true}"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toTopOf="parent"/>

        <TextView
            android:id="@+id/author_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="10dp"
            android:layout_marginTop="@dimen/dp_3"
            android:text="@{feed.author.name}"
            android:textColor="@{fullscreen?@color/color_white:@color/color_000}"
            android:textSize="@dimen/sp_14"
            android:textStyle="bold"
            app:layout_constraintLeft_toRightOf="@+id/author_avatar"
            app:layout_constraintTop_toTopOf="parent"
            tools:text="神秘的jetpack"/>

        <TextView
            android:id="@+id/create_time"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="10dp"
            android:layout_marginTop="@dimen/dp_2"
            android:text="@{TimeUtils.calculate(feed.createTime)}"
            android:textColor="@{fullscreen?@color/color_white:@color/color_000}"
            android:textSize="@dimen/sp_12"
            android:textStyle="normal"
            app:layout_constraintLeft_toRightOf="@+id/author_avatar"
            app:layout_constraintTop_toBottomOf="@+id/author_name"
            tools:text="3天前"/>

        <com.google.android.material.button.MaterialButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginRight="@dimen/dp_16"
            android:backgroundTint="@{fullscreen?@color/transparent:@color/color_theme}"
            android:gravity="center"
            android:paddingLeft="@dimen/dp_16"
            android:paddingTop="@dimen/dp_5"
            android:paddingRight="@dimen/dp_16"
            android:paddingBottom="@dimen/dp_5"
            android:text="@{feed.author.hasFollow?@string/has_follow:@string/unfollow}"
            android:textColor="@color/color_white"
            android:textSize="@dimen/sp_14"
            app:backgroundTint="@color/color_theme"
            app:cornerRadius="@dimen/dp_13"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:strokeColor="@{fullscreen?@color/color_white:@color/transparent}"
            app:strokeWidth="1dp"
            tools:text="已关注" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

图文详情页用户信息布局和下面的图文和下面的评论列表是可以一起滑动的,我们采用RecyclerView添加Header方式实现,所以我们需要给下面的评论列表写一个头部,
在这里插入图片描述

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>

        <variable
            name="feed"
            type="com.mooc.ppjoke.model.Feed" />

        <import type="com.mooc.libcommon.utils.PixUtils"/>
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/color_white"
        android:orientation="vertical">

        <include
            layout="@layout/layout_feed_detail_author_info"
            app:feed="@{feed}"
            app:fullscreen="@{false}"
            app:leftMargin="@{PixUtils.dp2px(16)}"/>

        <View
            android:layout_width="match_parent"
            android:layout_height="@dimen/dp_10"/>

        <include
            layout="@layout/layout_feed_text"
            app:feedText="@{feed.feeds_text}"
            app:lines="@{1000}"/>

        <com.mooc.libcommon.view.PPImageView
            android:id="@+id/header_image"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="@dimen/dp_10"/>

        <View
            android:layout_width="match_parent"
            android:layout_height="@dimen/dp_10"
            android:layout_marginTop="6dp"
            android:background="@color/color_divider"/>

    </LinearLayout>
</layout>

下面我们编写底部布局 评论+互动,互动区域要根据状态显示不同的图片,这里我们使用ImageView+TextView方式实现

<?xml version="1.0" encoding="utf-8"?>
<layout 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">

    <data>

        <variable
            name="feed"
            type="com.mooc.ppjoke.model.Feed" />

        <variable
            name="fullscreen"
            type="java.lang.Boolean" />

        <import type="android.view.View" />
        

    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="48dp"
        android:layout_gravity="bottom"
        android:background="@{fullscreen?@color/transparent:@color/color_white}"
        android:orientation="vertical">

        <View
            android:layout_width="match_parent"
            android:layout_height="1px"
            android:background="@color/color_divider"
            android:visibility="@{fullscreen?View.INVISIBLE:View.VISIBLE}">

        </View>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:orientation="horizontal">

            <TextView
                android:id="@+id/input_view"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_marginLeft="@dimen/dp_16"
                android:layout_marginTop="@dimen/dp_4"
                android:layout_marginRight="@dimen/dp_16"
                android:layout_marginBottom="@dimen/dp_4"
                android:layout_weight="4"
                android:background="@drawable/bg_edit_view"
                android:gravity="center_vertical"
                android:hint="@string/feed_detil_hint"
                android:paddingLeft="@dimen/dp_10"
                android:textColor="@{fullscreen?@color/color_white:@color/color_333}"
                android:textColorHint="@{fullscreen?@color/color_white:@color/color_333}"
                android:textSize="12sp">

            </TextView>

            <LinearLayout
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:gravity="center"
                android:orientation="vertical">

                <androidx.appcompat.widget.AppCompatImageView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="@dimen/dp_2"
                    android:background="@{feed.ugc.hasLiked?@drawable/icon_cell_liked:@drawable/icon_cell_like}"
                    android:backgroundTint="@{feed.ugc.hasLiked?@color/color_theme:fullscreen?@color/color_white:@color/color_666}"
                    tools:src="@drawable/icon_cell_liked">

                </androidx.appcompat.widget.AppCompatImageView>

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="@dimen/dp_2"
                    android:gravity="center"
                    android:text="@{feed.ugc.likeCount>0?String.valueOf(feed.ugc.likeCount):@string/like}"
                    android:textColor="@{feed.ugc.hasLiked?@color/color_theme:fullscreen?@color/color_white:@color/color_666}"
                    android:textSize="@dimen/sp_12"
                    tools:text=""/>
            </LinearLayout>


            <LinearLayout
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:gravity="center"
                android:orientation="vertical">

                <androidx.appcompat.widget.AppCompatImageView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="@dimen/dp_2"
                    android:background="@{feed.ugc.hasFavorite?@drawable/ic_collected:@drawable/ic_collect}"
                    android:backgroundTint="@{feed.ugc.hasFavorite?@color/color_theme:fullscreen?@color/color_white:@color/color_666}"
                    tools:src="@drawable/ic_collected">

                </androidx.appcompat.widget.AppCompatImageView>

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="@dimen/dp_2"
                    android:gravity="center"
                    android:text="@{feed.ugc.hasFavorite?@string/has_collect:@string/collect}"
                    android:textColor="@{feed.ugc.hasFavorite?@color/color_theme:fullscreen?@color/color_white:@color/color_666}"
                    android:textSize="@dimen/sp_12"
                    tools:text="收藏"/>
            </LinearLayout>


            <LinearLayout
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:gravity="center"
                android:orientation="vertical">

                <androidx.appcompat.widget.AppCompatImageView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="@dimen/dp_2"
                    android:background="@drawable/icon_cell_share"
                    android:backgroundTint="@{fullscreen?@color/color_white:@color/color_666}"
                    tools:src="@drawable/icon_cell_share">

                </androidx.appcompat.widget.AppCompatImageView>

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="@dimen/dp_2"
                    android:gravity="center"
                    android:text="@string/share"
                    android:textColor="@{fullscreen?@color/color_white:@color/color_666}"
                    android:textSize="@dimen/sp_12"
                    tools:text="分享"/>
            </LinearLayout>
        </LinearLayout>
    </LinearLayout>
</layout>

看下效果
在这里插入图片描述

接着我们就来搭建图片详情页整体布局,首先创建一个图文详情页布局文件

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>

        <variable
            name="feed"
            type="com.mooc.ppjoke.model.Feed" />

        <import type="com.mooc.libcommon.utils.PixUtils"/>
    </data>

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

        <FrameLayout
            android:id="@+id/title_layout"
            android:layout_width="match_parent"
            android:layout_height="@dimen/dp_50">

            <ImageView
                android:id="@+id/action_close"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center_vertical"
                android:paddingLeft="@dimen/dp_16"
                android:paddingRight="@dimen/dp_16"
                android:src="@drawable/icon_back_black"/>

            <TextView
                android:id="@+id/title"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:text="@string/feed_detail_type_image_title"
                android:textColor="@color/color_000"
                android:textSize="@dimen/sp_16"/>

            <include
                android:id="@+id/author_info_layout"
                layout="@layout/layout_feed_detail_author_info"
                android:visibility="gone"
                app:feed="@{feed}"
                app:fullscreen="@{false}"
                app:leftMargin="@{PixUtils.dp2px(60)}"/>

            <View
                android:layout_width="match_parent"
                android:layout_height="1dp"
                android:layout_gravity="bottom"
                android:background="@color/color_divider"/>
        </FrameLayout>

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/recycler_view"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"/>

        <include
            android:id="@+id/interaction_layout"
            layout="@layout/layout_feed_detail_bottom_inateraction"
            app:feed="@{feed}"
            app:fullscreen="@{false}"/>
    </LinearLayout>
</layout>

看下效果
在这里插入图片描述
三部分组成,顶部是标题和用户信息,中间是一个评论列表,底部是互动布局

Paging添加HeaderFooter

我们刚才说图文详情整体滑动我们采用的是RecyclerView添加Header方式实现,所以我们需要修改下我们的PagerListAdaper

/**
 * 一个能够添加HeaderView,FooterView的PagedListAdapter。
 * 解决了添加HeaderView和FooterView时 RecyclerView定位不准确的问题
 *
 * @param <T>  Java Bean
 * @param <VH>
 */
public abstract class AbsPagedListAdapter<T, VH extends RecyclerView.ViewHolder> extends PagedListAdapter<T, VH> {
    private SparseArray<View> mHeaders = new SparseArray<>();
    private SparseArray<View> mFooters = new SparseArray<>();

    private int BASE_ITEM_TYPE_HEADER = 100000;
    private int BASE_ITEM_TYPE_FOOTER = 200000;


    protected AbsPagedListAdapter(@NonNull DiffUtil.ItemCallback<T> diffCallback) {
        super(diffCallback);
    }

    public void addHeaderView(View view) {
        //判断给View对象是否还没有处在mHeaders数组里面
        if (mHeaders.indexOfValue(view) < 0) {
            mHeaders.put(BASE_ITEM_TYPE_HEADER++, view);
            notifyDataSetChanged();
        }
    }

    public void addFooterView(View view) {
        //判断给View对象是否还没有处在mFooters数组里面
        if (mFooters.indexOfValue(view) < 0) {
            mFooters.put(BASE_ITEM_TYPE_FOOTER++, view);
            notifyDataSetChanged();
        }
    }

    // 移除头部
    public void removeHeaderView(View view) {
        int index = mHeaders.indexOfValue(view);
        if (index < 0) {
            return;
        }
        mHeaders.removeAt(index);
        notifyDataSetChanged();
    }

    // 移除底部
    public void removeFooterView(View view) {
        int index = mFooters.indexOfValue(view);
        if (index < 0) {
            return;
        }
        mFooters.removeAt(index);
        notifyDataSetChanged();
    }

    public int getHeaderCount() {
        return mHeaders.size();
    }

    public int getFooterCount() {
        return mFooters.size();
    }

    @Override
    public int getItemCount() {
        int itemCount = super.getItemCount();
        return itemCount + mHeaders.size() + mFooters.size();
    }

    public int getOriginalItemCount() {
        return getItemCount() - mHeaders.size() - mFooters.size();
    }

    @Override
    public int getItemViewType(int position) {
        if (isHeaderPosition(position)) {
            //返回该position对应的headerview的  viewType
            return mHeaders.keyAt(position);
        }

        if (isFooterPosition(position)) {
            //footer类型的,需要计算一下它的position实际大小
            position = position - getOriginalItemCount() - mHeaders.size();
            return mFooters.keyAt(position);
        }
        position = position - mHeaders.size();
        return getItemViewType2(position);
    }

    protected int getItemViewType2(int position) {
        return 0;
    }

    private boolean isFooterPosition(int position) {
        return position >= getOriginalItemCount() + mHeaders.size();
    }

    private boolean isHeaderPosition(int position) {
        return position < mHeaders.size();
    }

    @NonNull
    @Override
    public VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        if (mHeaders.indexOfKey(viewType) >= 0) {
            View view = mHeaders.get(viewType);
            return (VH) new RecyclerView.ViewHolder(view) {
            };
        }

        if (mFooters.indexOfKey(viewType) >= 0) {
            View view = mFooters.get(viewType);
            return (VH) new RecyclerView.ViewHolder(view) {
            };
        }


        return onCreateViewHolder2(parent, viewType);
    }

    protected abstract VH onCreateViewHolder2(ViewGroup parent, int viewType);

    @Override
    public void onBindViewHolder(@NonNull VH holder, int position) {
        if (isHeaderPosition(position) || isFooterPosition(position)) {
            return;
        }
        //列表中正常类型的itemView的 position 咱们需要减去添加headerView的个数
        position = position - mHeaders.size();
        onBindViewHolder2(holder, position);
    }

    protected abstract void onBindViewHolder2(VH holder, int position);

    @Override
    public void onViewAttachedToWindow(@NonNull RecyclerView.ViewHolder holder) {
        if (!isHeaderPosition(holder.getAdapterPosition()) && !isFooterPosition(holder.getAdapterPosition())) {
            this.onViewAttachedToWindow2((VH) holder);
        }
    }

    public void onViewAttachedToWindow2(VH holder) {

    }

    @Override
    public void onViewDetachedFromWindow(@NonNull RecyclerView.ViewHolder holder) {
        if (!isHeaderPosition(holder.getAdapterPosition()) && !isFooterPosition(holder.getAdapterPosition())) {
            this.onViewDetachedFromWindow2((VH) holder);
        }
    }

    public void onViewDetachedFromWindow2(VH holder) {

    }

    @Override
    public void registerAdapterDataObserver(@NonNull RecyclerView.AdapterDataObserver observer) {
        super.registerAdapterDataObserver(new AdapterDataObserverProxy(observer));
    }

    //如果我们先添加了headerView,而后网络数据回来了再更新到列表上
    //由于Paging在计算列表上item的位置时 并不会顾及我们有没有添加headerView,就会出现列表定位的问题
    //实际上 RecyclerView#setAdapter方法,它会给Adapter注册了一个AdapterDataObserver
    //咱么可以代理registerAdapterDataObserver()传递进来的observer。在各个方法的实现中,把headerView的个数算上,再中转出去即可
    private class AdapterDataObserverProxy extends RecyclerView.AdapterDataObserver {
        private RecyclerView.AdapterDataObserver mObserver;

        public AdapterDataObserverProxy(RecyclerView.AdapterDataObserver observer) {
            mObserver = observer;
        }

        @Override
        public void onChanged() {
            mObserver.onChanged();
        }

        @Override
        public void onItemRangeChanged(int positionStart, int itemCount) {
            mObserver.onItemRangeChanged(positionStart + mHeaders.size(), itemCount);
        }

        @Override
        public void onItemRangeChanged(int positionStart, int itemCount, @Nullable Object payload) {
            mObserver.onItemRangeChanged(positionStart + mHeaders.size(), itemCount, payload);
        }

        @Override
        public void onItemRangeInserted(int positionStart, int itemCount) {
            mObserver.onItemRangeInserted(positionStart + mHeaders.size(), itemCount);
        }

        @Override
        public void onItemRangeRemoved(int positionStart, int itemCount) {
            mObserver.onItemRangeRemoved(positionStart + mHeaders.size(), itemCount);
        }

        @Override
        public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
            mObserver.onItemRangeMoved(fromPosition + mHeaders.size(), toPosition + mHeaders.size(), itemCount);
        }


    }
}

这里主要核心思想就是根据我们的HeaderFooter数量准确计算出实际的position,解决定位不准确的问题

OK,这一篇就到这里吧,下一篇我们将讲解图文详情页的实际数据展示

猜你喜欢

转载自blog.csdn.net/Greathfs/article/details/106244153