前言
这一篇开始我们编写帖子的详情页,帖子的类型有三种:图片,视频和文本,对应的样式也不相同,我们看下他们具体的样式
首先看下图片类型的:
第二张图片是我们往上滑动的时候显示的样式,顶部的标题会变成用户信息
我们看下视频的详情页:
第一张是视频的宽度大于高度的时候展示的样式,这个时候上面的视频是不动的,只能滑动中间的列表
第二张是视频的宽度小于高度的时候展示的样式,进来默认是全屏展示,用户信息布局和底部的布局背景都是透明
第三张是在第二张滑动的时候展示的样式,往上滑动的时候整个视频是主键缩小的,用户信息布局和底部布局背景变成白色
然后看下文本类型的详情页
这个就是固定的,中间是评论列表可滑动,基本上和图片的一样
分析
我们分析下这三种详情页,发现有两部分样式是通用的,用户信息和底部布局,这两部分布局我们独立出来,需要的地方我们使用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
添加Header
和Footer
我们刚才说图文详情整体滑动我们采用的是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);
}
}
}
这里主要核心思想就是根据我们的Header
和Footer
数量准确计算出实际的position
,解决定位不准确的问题
OK,这一篇就到这里吧,下一篇我们将讲解图文详情页的实际数据展示