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

前言

上一篇我们已经写完图文详情的布局展示,这一篇我们将完成图文详情的数据展示

实现

因为我们这个详情分为图文和视频,所以我们采用组合方式实现,相同的部分我们提取自父类中去完成

我们跳转到详情需要这个帖子的相关信息,所以我们需要传过来一个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);
        }

这个的这个viewHandlerViewHandler这个类,这个类是处理相同逻辑部分的,它对应的就是两个子类ImageViewHandlerVideoViewHandler,接着我们就通过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,列表大概样式如下
【Android】Jetpack全组件实战开发短视频应用App(十八)

<?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);
        }
    }

然后我们就要写底部互动区域的行为事件了,我们先看下都有哪些

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

点赞和分享我们之前写过了,我们主要就是写收藏和关注以及删除,我们还是在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,然后我们运行下看看效果
【Android】Jetpack全组件实战开发短视频应用App(十八)

上一篇:每日日报


下一篇:多态及实现方式