前言
上一篇我们已经写完图文详情的布局展示,这一篇我们将完成图文详情的数据展示
实现
因为我们这个详情分为图文和视频,所以我们采用组合方式实现,相同的部分我们提取自父类中去完成
我们跳转到详情需要这个帖子的相关信息,所以我们需要传过来一个Feed
对象
Feed feed = (Feed) getIntent().getSerializableExtra(KEY_FEED);
if (feed == null) {
finish();
return;
}
然后我们根据帖子ItemType
来区分
if (feed.itemType == Feed.TYPE_IMAGE_TEXT) {
viewHandler = new ImageViewHandler(this);
} else {
viewHandler = new VideoViewHandler(this);
}
这个的这个viewHandler
是ViewHandler
这个类,这个类是处理相同逻辑部分的,它对应的就是两个子类ImageViewHandler
和VideoViewHandler
,接着我们就通过bindInitData
方法对ViewHandler
设置,所以我们详情页的onCreate
方法如下
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Feed feed = (Feed) getIntent().getSerializableExtra(KEY_FEED);
if (feed == null) {
finish();
return;
}
if (feed.itemType == Feed.TYPE_IMAGE_TEXT) {
viewHandler = new ImageViewHandler(this);
} else {
viewHandler = new VideoViewHandler(this);
}
viewHandler.bindInitData(feed);
}
然后我们回到ViewHandler
这个类中,这个类我们需要做列表数据绑定,底部互动绑定以及行为处理,所以这个类里面需要有RecyclerView
对象和底部DataBinding
对象,并且我们在我们的bindInitData
方法中初始化一下
protected FragmentActivity mActivity;
protected Feed mFeed;
protected RecyclerView mRecyclerView;
protected LayoutFeedDetailBottomInateractionBinding mInateractionBinding;
protected FeedCommentAdapter mListAdapter;
public ViewHandler(FragmentActivity activity) {
mActivity = activity;;
}
@CallSuper
public void bindInitData(Feed feed) {
mInateractionBinding.setOwner(mActivity);
mFeed = feed;
mRecyclerView.setLayoutManager(new LinearLayoutManager(mActivity, LinearLayoutManager.VERTICAL, false));
mRecyclerView.setItemAnimator(null);
mListAdapter = new FeedCommentAdapter(mActivity);
mRecyclerView.setAdapter(mListAdapter);
}
这里我们需要这个列表写一个适配器FeedCommentAdapter
,列表大概样式如下
<?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="comment"
type="com.mooc.ppjoke.model.Comment" />
<import type="android.text.TextUtils"/>
<import type="android.view.View"/>
<import type="com.mooc.ppjoke.ui.login.UserManager"/>
<import type="com.mooc.ppjoke.utils.TimeUtils"/>
<import type="com.mooc.ppjoke.ui.InterActionPresenter"/>
<variable
name="owner"
type="androidx.lifecycle.LifecycleOwner" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingBottom="@dimen/dp_10">
<com.mooc.libcommon.view.PPImageView
android:id="@+id/author_avatar"
android:layout_width="@dimen/dp_40"
android:layout_height="@dimen/dp_40"
android:layout_marginLeft="@dimen/dp_16"
android:layout_marginTop="@dimen/dp_16"
app:image_url="@{comment.author.avatar}"
app:isCircle="@{true}"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@drawable/splash"/>
<TextView
android:id="@+id/author_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/dp_10"
android:layout_marginTop="@dimen/dp_16"
android:text="@{comment.author.name}"
android:textColor="@color/color_000"
android:textSize="@dimen/sp_12"
app:layout_constraintLeft_toRightOf="@+id/author_avatar"
app:layout_constraintTop_toTopOf="parent"
tools:text="神秘的jetpack"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/label_author"
android:layout_width="wrap_content"
android:layout_height="@dimen/dp_14"
android:layout_marginLeft="@dimen/dp_10"
android:layout_marginTop="@dimen/dp_16"
android:gravity="center"
android:includeFontPadding="false"
android:paddingLeft="@dimen/dp_5"
android:paddingTop="@dimen/dp_0"
android:paddingRight="@dimen/dp_5"
android:paddingBottom="@dimen/dp_0"
android:stateListAnimator="@null"
android:text="@string/author"
android:textColor="@color/color_white"
android:textSize="10sp"
app:backgroundTint="@color/color_theme"
app:cornerRadius="@dimen/dp_3"
app:layout_constraintBaseline_toBaselineOf="@+id/author_name"
app:layout_constraintLeft_toRightOf="@+id/author_name"
app:layout_constraintTop_toTopOf="parent"/>
<TextView
android:id="@+id/create_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/dp_10"
android:layout_marginTop="@dimen/dp_16"
android:text="@{TimeUtils.calculate(comment.createTime)}"
android:textColor="@color/color_999"
android:textSize="10sp"
app:layout_constraintBaseline_toBaselineOf="@+id/author_name"
app:layout_constraintLeft_toRightOf="@+id/label_author"
app:layout_constraintTop_toTopOf="parent"
tools:text="12天前"/>
<TextView
android:id="@+id/comment_like"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/dp_16"
android:layout_marginRight="16dp"
android:drawableRight="@{comment.ugc.hasLiked?@drawable/icon_cell_liked:@drawable/icon_cell_like}"
android:drawablePadding="@dimen/dp_3"
android:gravity="center_vertical"
android:includeFontPadding="false"
android:onClick="@{()->InterActionPresenter.toggleCommentLike(owner,comment)}"
android:text="@{String.valueOf(comment.ugc.likeCount)}"
android:textColor="@{comment.ugc.hasLiked?@color/color_theme:@color/color_999}"
android:textSize="@dimen/sp_10"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:drawableRight="@drawable/icon_cell_liked_large"
tools:text="1000"/>
<ImageView
android:id="@+id/comment_delete"
android:layout_width="@dimen/dp_14"
android:layout_height="@dimen/dp_14"
android:layout_marginRight="@dimen/dp_10"
android:src="@drawable/icon_item_cell_delete"
app:layout_constraintBottom_toBottomOf="@id/comment_like"
app:layout_constraintRight_toLeftOf="@+id/comment_like"
app:layout_constraintTop_toTopOf="@id/comment_like"/>
<TextView
android:id="@+id/comment_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/dp_10"
android:layout_marginTop="@dimen/dp_5"
android:gravity="center_vertical"
android:includeFontPadding="false"
android:text="@{comment.commentText}"
android:textColor="@color/color_333"
android:textSize="@dimen/sp_14"
app:layout_constraintHorizontal_weight="1"
app:layout_constraintLeft_toRightOf="@+id/author_avatar"
app:layout_constraintRight_toLeftOf="@+id/comment_like"
app:layout_constraintTop_toBottomOf="@+id/author_name"
tools:text="comment.commentText"/>
<FrameLayout
android:id="@+id/comment_ext"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/dp_10"
android:layout_marginTop="@dimen/dp_10"
app:layout_constraintLeft_toRightOf="@+id/author_avatar"
app:layout_constraintTop_toBottomOf="@id/comment_text">
<com.mooc.libcommon.view.PPImageView
android:id="@+id/comment_cover"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="@color/color_gray2"
android:scaleType="center"
tools:layout_height="100dp"
tools:layout_width="100dp">
</com.mooc.libcommon.view.PPImageView>
<ImageView
android:id="@+id/video_icon"
android:layout_width="@dimen/dp_25"
android:layout_height="@dimen/dp_25"
android:layout_gravity="center"
android:src="@drawable/icon_video_play"/>
</FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
然后我们写这个适配器的Java
代码
public class FeedCommentAdapter extends AbsPagedListAdapter<Comment, FeedCommentAdapter.ViewHolder> {
private LayoutInflater mInflater;
private Context mContext;
protected FeedCommentAdapter(Context context) {
super(new DiffUtil.ItemCallback<Comment>() {
@Override
public boolean areItemsTheSame(@NonNull Comment oldItem, @NonNull Comment newItem) {
return oldItem.id == newItem.id;
}
@Override
public boolean areContentsTheSame(@NonNull Comment oldItem, @NonNull Comment newItem) {
return oldItem.equals(newItem);
}
});
mContext = context;
mInflater = LayoutInflater.from(context);
}
@Override
protected ViewHolder onCreateViewHolder2(ViewGroup parent, int viewType) {
LayoutFeedCommentListItemBinding binding = LayoutFeedCommentListItemBinding.inflate(mInflater, parent, false);
return new ViewHolder(binding.getRoot(), binding);
}
@Override
protected void onBindViewHolder2(ViewHolder holder, int position) {
Comment item = getItem(position);
holder.bindData(item);
}
public class ViewHolder extends RecyclerView.ViewHolder {
private LayoutFeedCommentListItemBinding mBinding;
public ViewHolder(@NonNull View itemView, LayoutFeedCommentListItemBinding binding) {
super(itemView);
mBinding = binding;
}
public void bindData(Comment item) {
mBinding.setComment(item);
boolean self = item.author != null && UserManager.get().getUserId() == item.author.userId;
mBinding.labelAuthor.setVisibility(self ? View.VISIBLE : View.GONE);
mBinding.commentDelete.setVisibility(self ? View.VISIBLE : View.GONE);
if (!TextUtils.isEmpty(item.imageUrl)) {
mBinding.commentExt.setVisibility(View.VISIBLE);
mBinding.commentCover.setVisibility(View.VISIBLE);
mBinding.commentCover.bindData(item.width, item.height, 0, PixUtils.dp2px(200), PixUtils.dp2px(200), item.imageUrl);
if (!TextUtils.isEmpty(item.videoUrl)) {
mBinding.videoIcon.setVisibility(View.VISIBLE);
} else {
mBinding.videoIcon.setVisibility(View.GONE);
}
} else {
mBinding.commentCover.setVisibility(View.GONE);
mBinding.videoIcon.setVisibility(View.GONE);
mBinding.commentExt.setVisibility(View.GONE);
}
}
}
}
我们写完评论列表之后,先完成图文详情页的头部数据绑定,并且给这个RecyclerView
添加这个头部,我们回到ImageViewHandler
这个类,这里有两个DataBinding
,一个是图文详情的,我们通过这个拿到列表和底部互动布局,一个是头部的
public class ImageViewHandler extends ViewHandler {
protected ActivityFeedDetailTypeImageBinding mImageBinding;
protected LayoutFeedDetailTypeImageHeaderBinding mHeaderBinding;
public ImageViewHandler(FragmentActivity activity) {
super(activity);
mImageBinding = DataBindingUtil.setContentView(activity, R.layout.activity_feed_detail_type_image);
mInateractionBinding = mImageBinding.interactionLayout;
mRecyclerView = mImageBinding.recyclerView;
}
@Override
public void bindInitData(Feed feed) {
super.bindInitData(feed);
mImageBinding.setFeed(mFeed);
mHeaderBinding = LayoutFeedDetailTypeImageHeaderBinding.inflate(LayoutInflater.from(mActivity), mRecyclerView, false);
mHeaderBinding.setFeed(mFeed);
PPImageView headerImage = mHeaderBinding.headerImage;
headerImage.bindData(mFeed.width, mFeed.height, mFeed.width > mFeed.height ? 0 : 16, mFeed.cover);
mListAdapter.addHeaderView(mHeaderBinding.getRoot());
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
boolean visible = mHeaderBinding.getRoot().getTop() <= -mImageBinding.titleLayout.getMeasuredHeight();
mImageBinding.authorInfoLayout.getRoot().setVisibility(visible ? View.VISIBLE : View.GONE);
mImageBinding.title.setVisibility(visible ? View.GONE : View.VISIBLE);
}
});
}
}
这里我们是监听列表的滑动,滑动一定的距离后我们就需要改变UI样式
OK,接着我们就可以写网络请求了,首先是获取评论列表,我们新建一个ViewModel
,在这个类里面做数据请求
public class FeedDetailViewModel extends AbsViewModel<Comment> {
private long itemId;
@Override
public DataSource createDataSource() {
return new DataSource();
}
public void setItemId(long itemId) {
this.itemId = itemId;
}
class DataSource extends ItemKeyedDataSource<Integer, Comment> {
@Override
public void loadInitial(@NonNull LoadInitialParams<Integer> params, @NonNull LoadInitialCallback<Comment> callback) {
loadData(params.requestedInitialKey, params.requestedLoadSize, callback);
}
@Override
public void loadAfter(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<Comment> callback) {
if (params.key > 0) {
loadData(params.key, params.requestedLoadSize, callback);
}
}
private void loadData(Integer key, int requestedLoadSize, LoadCallback<Comment> callback) {
ApiResponse<List<Comment>> response = ApiService.get("/comment/queryFeedComments")
.addParam("id", key)
.addParam("itemId", itemId)
.addParam("userId", UserManager.get().getUserId())
.addParam("pageCount", requestedLoadSize)
.responseType(new TypeReference<ArrayList<Comment>>() {
}.getType())
.execute();
List<Comment> list = response.body == null ? Collections.emptyList() : response.body;
callback.onResult(list);
}
@Override
public void loadBefore(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<Comment> callback) {
callback.onResult(Collections.emptyList());
}
@NonNull
@Override
public Integer getKey(@NonNull Comment item) {
return item.id;
}
}
}
然后我们回到ViewHandler
中触发加载逻辑
首先通过ViewModelProviders
拿到我们的ViewModel
对象
public ViewHandler(FragmentActivity activity) {
mActivity = activity;
viewModel = ViewModelProviders.of(activity).get(FeedDetailViewModel.class);
}
接着就是在bindInitData
触发
viewModel.setItemId(mFeed.itemId);
viewModel.getPageData().observe(mActivity, new Observer<PagedList<Comment>>() {
@Override
public void onChanged(PagedList<Comment> comments) {
mListAdapter.submitList(comments);
handleEmpty(comments.size() > 0);
}
});
private EmptyView mEmptyView;
public void handleEmpty(boolean hasData) {
if (hasData) {
if (mEmptyView != null) {
mListAdapter.removeHeaderView(mEmptyView);
}
} else {
if (mEmptyView == null) {
mEmptyView = new EmptyView(mActivity);
RecyclerView.LayoutParams layoutParams = new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
layoutParams.topMargin = PixUtils.dp2px(40);
mEmptyView.setLayoutParams(layoutParams);
mEmptyView.setTitle(mActivity.getString(R.string.feed_comment_empty));
}
mListAdapter.addHeaderView(mEmptyView);
}
}
然后我们就要写底部互动区域的行为事件了,我们先看下都有哪些
点赞和分享我们之前写过了,我们主要就是写收藏和关注以及删除,我们还是在InterActionPresenter
这个类中完成
//收藏/取消收藏一个帖子
public static void toggleFeedFavorite(LifecycleOwner owner, Feed feed) {
if (!isLogin(owner, new Observer<User>() {
@Override
public void onChanged(User user) {
toggleFeedFavorite(feed);
}
})) {
} else {
toggleFeedFavorite(feed);
}
}
private static void toggleFeedFavorite(Feed feed) {
ApiService.get("/ugc/toggleFavorite")
.addParam("itemId", feed.itemId)
.addParam("userId", UserManager.get().getUserId())
.execute(new JsonCallback<JSONObject>() {
@Override
public void onSuccess(ApiResponse<JSONObject> response) {
if (response.body != null) {
boolean hasFavorite = response.body.getBooleanValue("hasFavorite");
feed.getUgc().setHasFavorite(hasFavorite);
}
}
@Override
public void one rror(ApiResponse<JSONObject> response) {
showToast(response.message);
}
});
}
//关注/取消关注一个用户
public static void toggleFollowUser(LifecycleOwner owner, Feed feed) {
if (!isLogin(owner, new Observer<User>() {
@Override
public void onChanged(User user) {
toggleFollowUser(feed);
}
})) {
} else {
toggleFollowUser(feed);
}
}
private static void toggleFollowUser(Feed feed) {
ApiService.get("/ugc/toggleUserFollow")
.addParam("followUserId", UserManager.get().getUserId())
.addParam("userId", feed.author.userId)
.execute(new JsonCallback<JSONObject>() {
@Override
public void onSuccess(ApiResponse<JSONObject> response) {
if (response.body != null) {
boolean hasFollow = response.body.getBooleanValue("hasLiked");
feed.getAuthor().setHasFollow(hasFollow);
}
}
@Override
public void one rror(ApiResponse<JSONObject> response) {
showToast(response.message);
}
});
}
这里主要实现和之前差不多,就不说明了,然后我们在对应的xml
中添加上点击事件即可,做完这些之后我们把帖子列表的Item
点击事件加上FeedAdapter
中
@Override
protected void onBindViewHolder2(ViewHolder holder, int position) {
final Feed feed = getItem(position);
holder.bindData(feed);
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
FeedDetailActivity.startFeedDetailActivity(mContext, feed, mCategory);
}
});
}
我们给我们的详情页添加一个跳转方法
private static final String KEY_FEED = "key_feed";
public static final String KEY_CATEGORY = "key_category";
public static void startFeedDetailActivity(Context context, Feed item, String category) {
Intent intent = new Intent(context, FeedDetailActivity.class);
intent.putExtra(KEY_FEED, item);
intent.putExtra(KEY_CATEGORY, category);
context.startActivity(intent);
}
OK,然后我们运行下看看效果