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

前言

项目地址
我们已经把首页列表的Item布局完成,接下来我们就开始加载首页数据,我们这一篇主要是做封装,具体网络请求放在下一篇

引入依赖

//刷新分页组件
api 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0'
api 'com.scwang.smartrefresh:SmartRefreshHeader:1.1.0'

//viewmodel and livedata
api 'androidx.lifecycle:lifecycle-extensions:2.1.0'   

//paging分页组件
api 'androidx.paging:paging-runtime:2.1.0'

我们首页有下拉刷新和上拉加载,我们使用SmartRefreshLayout实现,分页使用paging,viewmodellivedata我们导入了一个库lifecycle-extensions,当然你也可以分开导入

封装刷新组件

我们知道列表在加载的时候,如果没有数据,我们一般会展示一个空的布局,所以我们先自定义一个EmptyView,布局很简单,

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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="wrap_content"
    android:layout_gravity="center"
    android:gravity="center_horizontal"
    android:orientation="vertical">


    <ImageView
        android:id="@+id/empty_icon"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/icon_empty_no_data" />

    <TextView
        android:id="@+id/empty_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:gravity="center"
        android:text="虽然什么也没有,要不下拉刷新试试"
        android:textColor="#999999"
        android:textSize="16sp"
        android:visibility="visible"
        tools:text="虽然什么也没有,要不下拉刷新看看"/>


    <com.google.android.material.button.MaterialButton
        android:id="@+id/empty_action"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:paddingLeft="16dp"
        android:paddingRight="16dp"

        android:textColor="#ffffff"
        android:textSize="14sp"
        android:visibility="gone"
        app:backgroundTint="#ff678f"
        app:cornerRadius="20dp"
        tools:text="朕知道了">

    </com.google.android.material.button.MaterialButton>
</LinearLayout>

然后我们创建一个EmptyView来加载这个xml,同时添加几个set方法

/**
 * 空布局
 */
public class EmptyView extends LinearLayout {
    private ImageView icon;
    private TextView title;
    private Button action;

    public EmptyView(@NonNull Context context) {
        this(context, null);
    }

    public EmptyView(@NonNull Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public EmptyView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, 0);
    }

    public EmptyView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int style) {
        super(context, attrs, defStyleAttr, style);
        setOrientation(VERTICAL);
        setGravity(Gravity.CENTER);
        LayoutInflater.from(context).inflate(R.layout.layout_empty_view, this, true);

        icon = findViewById(R.id.empty_icon);
        title = findViewById(R.id.empty_text);
        action = findViewById(R.id.empty_action);
    }


    public void setEmptyIcon(@DrawableRes int iconRes) {
        icon.setImageResource(iconRes);
    }

    public void setTitle(String text) {
        if (TextUtils.isEmpty(text)) {
            title.setVisibility(GONE);
        } else {
            title.setText(text);
            title.setVisibility(VISIBLE);
        }

    }

    public void setButton(String text, OnClickListener listener) {
        if (TextUtils.isEmpty(text)) {
            action.setVisibility(GONE);
        } else {
            action.setText(text);
            action.setVisibility(VISIBLE);
            action.setOnClickListener(listener);
        }

    }
}

接着就是编写我们的刷新布局了,由三部分组成,从上往下分别是头部刷新布局,列表,底部刷新布局,SmartRefreshLayout都给我们提供了,我们只需要拿来直接使用即可

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

    <data>

    </data>

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

        <com.scwang.smartrefresh.layout.SmartRefreshLayout
            android:id="@+id/refresh_layout"
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <com.scwang.smartrefresh.layout.header.ClassicsHeader
                android:layout_width="match_parent"
                android:layout_height="wrap_content"/>

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

            <com.scwang.smartrefresh.layout.footer.ClassicsFooter
                android:layout_width="match_parent"
                android:layout_height="wrap_content"/>

        </com.scwang.smartrefresh.layout.SmartRefreshLayout>

        <com.hfs.libcommon.view.EmptyView
            android:id="@+id/empty_view"
            android:layout_width="match_parent"
            android:layout_marginTop="100dp"
            android:layout_height="match_parent"/>
    </FrameLayout>
</layout>

封装列表Fragment

接下来我们就封装一个AbsListFragment,这个是所有列表数据Fragment的基类,里面包括上拉加载/下拉刷新等操作,

/**
 * 列表Fragment,下拉刷新/上拉加载
 */
public abstract class AbsListFragment<T> extends Fragment implements OnRefreshListener, OnLoadMoreListener {

    private LayoutRefreshViewBinding mBinding;
    protected RecyclerView mRecyclerView;
    protected SmartRefreshLayout mRefreshLayout;
    protected EmptyView mEmptyView;

    protected PagedListAdapter<T, RecyclerView.ViewHolder> mAdapter;

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        mBinding = LayoutRefreshViewBinding.inflate(inflater, container, false);

        mRecyclerView = mBinding.recyclerView;
        mRefreshLayout = mBinding.refreshLayout;
        mEmptyView = mBinding.emptyView;

        mRefreshLayout.setEnableRefresh(true);
        mRefreshLayout.setEnableLoadMore(true);
        mRefreshLayout.setOnRefreshListener(this);
        mRefreshLayout.setOnLoadMoreListener(this);

        mAdapter = getAdapter();
        mRecyclerView.setAdapter(mAdapter);
        mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, false));
        mRecyclerView.setItemAnimator(null);

        //默认给列表中的Item 一个 10dp的ItemDecoration
        DividerItemDecoration decoration = new DividerItemDecoration(getContext(), LinearLayoutManager.VERTICAL);
        decoration.setDrawable(ContextCompat.getDrawable(getContext(), R.drawable.list_divider));
        mRecyclerView.addItemDecoration(decoration);
        return mBinding.getRoot();
    }

    @Override
    public void onRefresh(@NonNull RefreshLayout refreshLayout) {

    }

    @Override
    public void onLoadMore(@NonNull RefreshLayout refreshLayout) {

    }

    public abstract PagedListAdapter<T, RecyclerView.ViewHolder> getAdapter();
}

我们先设置一些基本属性方法,包括上拉加载监听,下拉刷新监听,RecyclerView设置适配器等
不管是下拉刷新还是上拉加载都有回调,我们把这些回调的处理也写在这个Fragment中,方便我们后续使用

   public void finishRefresh(boolean hasData) {
   		//当前列表数据
        PagedList<T> currentList = mAdapter.getCurrentList();
        //是否有数据
        hasData = hasData || currentList != null && currentList.size() > 0;
        RefreshState state = mRefreshLayout.getState();
        if (state.isFooter && state.isOpening) {
            mRefreshLayout.finishLoadMore();
        } else if (state.isHeader && state.isOpening) {
            mRefreshLayout.finishRefresh();
        }

        if (hasData) {
            mEmptyView.setVisibility(View.GONE);
        } else {
            mEmptyView.setVisibility(View.VISIBLE);
        }
    }

我们这里还需要把数据展示在我们的列表上,通过PagedListAdaptersubmitList方法可以实现

   public void submitList(PagedList<T> pagedList) {
        if (pagedList.size() > 0) {
            mAdapter.submitList(pagedList);
        }
        finishRefresh(pagedList.size()>0);
    }

OK,到目前我们这个AbsListFragment基本写完了,接下来我们让我们的HomeFragment实现它即可
在这里插入图片描述

首页列表适配器

我们实现了AbsListFragment后,会实现一个getAdapter方法,所以我们还需要创建一个Adaper,这个Adapter继承自PagedListAdapter

public class FeedAdapter extends PagedListAdapter<Feed,FeedAdapter.ViewHolder> {
    protected FeedAdapter() {
        super(new DiffUtil.ItemCallback<Feed>() {
            @Override
            public boolean areItemsTheSame(@NonNull Feed oldItem, @NonNull Feed newItem) {
                return false;
            }

            @Override
            public boolean areContentsTheSame(@NonNull Feed oldItem, @NonNull Feed newItem) {
                return false;
            }
        });
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return null;
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {

    }

    public class ViewHolder extends RecyclerView.ViewHolder {
        public ViewHolder(@NonNull View itemView) {
            super(itemView);
        }
    }
}

我们在继承PagedListAdapter的时候,我们的构造方法必须有一个DiffUtil.ItemCallback,这个是新老数据对比时用的,areItemsTheSame判断对象是否相同,areContentsTheSame判断内容是否相同,我们看下源码注释
在这里插入图片描述

在这里插入图片描述
第一个方法建议使用id,第二个方法建议使用equals方法,所以我们重写下我们每个Beanequals方法,然后我们这里就可以这样写

 protected FeedAdapter() {
        super(new DiffUtil.ItemCallback<Feed>() {
            @Override
            public boolean areItemsTheSame(@NonNull Feed oldItem, @NonNull Feed newItem) {
                return oldItem.id==newItem.id;
            }

            @Override
            public boolean areContentsTheSame(@NonNull Feed oldItem, @NonNull Feed newItem) {
                return oldItem.equals(newItem);
            }
        });
    }

接下来这个Adapter就跟我们平时使用的Adapter差不多了,这里因为有可能是图片,也有可能是视频,所以需要根据itemType做下区分,完整代码如下

public class FeedAdapter extends PagedListAdapter<Feed, FeedAdapter.ViewHolder> {

    private final LayoutInflater mLayoutInflater;
    protected String mCategory;

    protected FeedAdapter(Context context,String category) {
        super(new DiffUtil.ItemCallback<Feed>() {
            @Override
            public boolean areItemsTheSame(@NonNull Feed oldItem, @NonNull Feed newItem) {
                return oldItem.id == newItem.id;
            }

            @Override
            public boolean areContentsTheSame(@NonNull Feed oldItem, @NonNull Feed newItem) {
                return oldItem.equals(newItem);
            }
        });
        mLayoutInflater = LayoutInflater.from(context);
        mCategory = category;
    }

    @Override
    public int getItemViewType(int position) {
        Feed feed = getItem(position);
        return feed.itemType;
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        ViewDataBinding binding = null;
        if (viewType == Feed.TYPE_IMAGE_TEXT) {
            binding = LayoutFeedTypeImageBinding.inflate(mLayoutInflater, parent, false);
        } else {
            binding = LayoutFeedTypeVideoBinding.inflate(mLayoutInflater, parent, false);
        }
        return new ViewHolder(binding.getRoot(),binding);
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        holder.bindData(getItem(position));
    }

    public class ViewHolder extends RecyclerView.ViewHolder {

        private ViewDataBinding mBinding;
        public ViewHolder(@NonNull View itemView, ViewDataBinding binding) {
            super(itemView);
            mBinding = binding;
        }

        public void bindData(Feed item) {
            if (mBinding instanceof LayoutFeedTypeImageBinding) {
                LayoutFeedTypeImageBinding imageBinding = (LayoutFeedTypeImageBinding) mBinding;
                imageBinding.setFeed(item);
                imageBinding.feedImage.bindData(item.width, item.height, 16, item.cover);
            } else if (mBinding instanceof LayoutFeedTypeVideoBinding) {
                LayoutFeedTypeVideoBinding videoBinding = (LayoutFeedTypeVideoBinding) mBinding;
                videoBinding.setFeed(item);
                videoBinding.listPlayerView.bindData(mCategory, item.width, item.height, item.cover, item.url);
            }
        }
    }
}

下一篇我们将使用Paging分页加载和网络请求数据展示

猜你喜欢

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