TouTiao开源项目 分析笔记13 最后一个订阅号的实现主页面

Posted Jason_Jan

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了TouTiao开源项目 分析笔记13 最后一个订阅号的实现主页面相关的知识,希望对你有一定的参考价值。

1.实现订阅号的基础类

1.1.本地订阅号的Bean类==>MediaChannelBean

public class MediaChannelBean implements Parcelable {

    public static final Creator<MediaChannelBean> CREATOR = new Creator<MediaChannelBean>() {
        @Override
        public MediaChannelBean createFromParcel(Parcel in) {
            return new MediaChannelBean(in);
        }

        @Override
        public MediaChannelBean[] newArray(int size) {
            return new MediaChannelBean[size];
        }
    };
    private String id;
    private String name;
    private String avatar;
    private String type;
    private String followCount;
    private String descText;
    private String url;

    public MediaChannelBean() {
    }

    protected MediaChannelBean(Parcel in) {
        id = in.readString();
        name = in.readString();
        avatar = in.readString();
        type = in.readString();
        followCount = in.readString();
        descText = in.readString();
        url = in.readString();
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(id);
        dest.writeString(name);
        dest.writeString(avatar);
        dest.writeString(type);
        dest.writeString(followCount);
        dest.writeString(descText);
        dest.writeString(url);
    }

    @Override
    public int describeContents() {
        return 0;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAvatar() {
        return avatar;
    }

    public void setAvatar(String avatar) {
        this.avatar = avatar;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public String getFollowCount() {
        return followCount;
    }

    public void setFollowCount(String followCount) {
        this.followCount = followCount;
    }

    public String getDescText() {
        return descText;
    }

    public void setDescText(String descText) {
        this.descText = descText;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }
}

 

 

1.2.数据库建立订阅号的基础表==>MediaChannelTable 

public class MediaChannelTable {
    /**
     * 头条号信息表
     */
    public static final String TABLENAME = "MediaChannelTable";

    /**
     * 字段部分
     */
    public static final String ID = "id";
    public static final String NAME = "name";
    public static final String AVATAR = "avatar";
    public static final String TYPE = "type";
    public static final String FOLLOWCOUNT = "followCount";
    public static final String DESCTEXT = "descText";
    public static final String URL = "url";

    /**
     * 字段ID 数据库操作建立字段对应关系 从0开始
     */
    public static final int ID_ID = 0;
    public static final int ID_NAME = 1;
    public static final int ID_AVATAR = 2;
    public static final int ID_TYPE = 3;
    public static final int ID_FOLLOWCOUNT = 4;
    public static final int ID_DESCTEXT = 5;
    public static final int ID_URL = 6;

    /**
     * 创建表
     */
    public static final String CREATE_TABLE = "create table if not exists " + TABLENAME + "(" +
            ID + " text primary key, " +
            NAME + " text, " +
            AVATAR + " text, " +
            TYPE + " text, " +
            FOLLOWCOUNT + " text, " +
            DESCTEXT + " text, " +
            URL + " text) ";
}

 

 

1.3.实现最底层订阅号的数据库操作

  

public class MediaChannelDao {
    private SQLiteDatabase db;

    public MediaChannelDao(){
        this.db= DatabaseHelper.getDatabase();
    }

    public void initData(){
        add("4377795668", "新华网", "http://p2.pstatp.com/large/3658/7378365093", "news",
                "", "传播中国,报道世界;权威声音,亲切表达。", "http://toutiao.com/m4377795668/");
        add("52445544609", "互联网的这点事", "http://p3.pstatp.com/large/ef300164e786ff295da", "news",
                "", "每天为你速递最新、最鲜、最有料的互联网科技资讯!", "http://toutiao.com/m52445544609/");
    }

    public boolean add(String id,
                       String name,
                       String avatar,
                       String type,
                       String followCount,
                       String descText,
                       String url) {
        ContentValues values = new ContentValues();
        values.put(MediaChannelTable.ID, id);
        values.put(MediaChannelTable.NAME, name);
        values.put(MediaChannelTable.AVATAR, avatar);
        values.put(MediaChannelTable.TYPE, type);
        values.put(MediaChannelTable.FOLLOWCOUNT, followCount);
        values.put(MediaChannelTable.DESCTEXT, descText);
        values.put(MediaChannelTable.URL, url);
        long result = db.insert(MediaChannelTable.TABLENAME, null, values);
        return result != -1;
    }

    public List<MediaChannelBean> queryAll() {
        Cursor cursor = db.query(MediaChannelTable.TABLENAME, null, null, null, null, null, null);
        List<MediaChannelBean> list = new ArrayList<>();
        while (cursor.moveToNext()) {
            MediaChannelBean bean = new MediaChannelBean();
            bean.setId(cursor.getString(MediaChannelTable.ID_ID));
            bean.setName(cursor.getString(MediaChannelTable.ID_NAME));
            bean.setAvatar(cursor.getString(MediaChannelTable.ID_AVATAR));
            bean.setType(cursor.getString(MediaChannelTable.ID_TYPE));
            bean.setFollowCount(cursor.getString(MediaChannelTable.ID_FOLLOWCOUNT));
            bean.setDescText(cursor.getString(MediaChannelTable.ID_DESCTEXT));
            bean.setUrl(cursor.getString(MediaChannelTable.ID_URL));
            list.add(bean);
        }
        cursor.close();
        return list;
    }

    public boolean queryIsExist(String id) {
        Cursor cursor = db.query(MediaChannelTable.TABLENAME, null, MediaChannelTable.ID + "=?", new String[]{id}, null, null, null);
        if (cursor.moveToNext()) {
            cursor.close();
            return true;
        }
        cursor.close();
        return false;
    }

    public boolean delete(String mediaId) {
        int id = db.delete(MediaChannelTable.TABLENAME, MediaChannelTable.ID + "=?", new String[]{mediaId});
        return id != -1;
    }
}

  


2.构建订阅号视图页面

2.1.创建订阅号视图布局==>fragment_media.xml 

<?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"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/windowBackground"
    android:orientation="vertical"
    app:layout_behavior="@string/appbar_scrolling_view_behavior">

    <android.support.v4.widget.SwipeRefreshLayout
        android:id="@+id/refresh_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <android.support.design.widget.CoordinatorLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <TextView
                android:id="@+id/tv_desc"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:text="@string/media_hint_desc"/>

            <android.support.v7.widget.RecyclerView
                android:id="@+id/recycler_view"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:fadeScrollbars="true"
                android:scrollbarFadeDuration="1"
                android:scrollbars="vertical"/>

        </android.support.design.widget.CoordinatorLayout>

    </android.support.v4.widget.SwipeRefreshLayout>

</LinearLayout>

  预览图片:

  

 

 

2.2.定义一个长按监听事件==>长按弹出提示框 

public interface IOnItemLongClickListener {

    /**
     * RecyclerView Item长按事件
     */
    void onLongClick(View view, int position);
}

 

 

 

2.3.构建一个订阅号视图类 

public class MediaChannelView extends RxFragment implements SwipeRefreshLayout.OnRefreshListener {

    private static final String TAG = "MediaChannelView";
    private static MediaChannelView instance = null;
    private RecyclerView recyclerView;
    private SwipeRefreshLayout swipeRefreshLayout;
    private MultiTypeAdapter adapter;
    private MediaChannelDao dao = new MediaChannelDao();
    private TextView tv_desc;
    private String isFirstTime = "isFirstTime";
    private List<MediaChannelBean> list;

    public static MediaChannelView getInstance() {
        if (instance == null) {
            instance = new MediaChannelView();
        }
        return instance;
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_media, container, false);
        initView(view);
        initData();
        return view;
    }

    @Override
    public void onResume() {
        super.onResume();
        swipeRefreshLayout.setColorSchemeColors(SettingUtil.getInstance().getColor());
        setAdapter();
    }

    private void initData() {
        SharedPreferences editor = getActivity().getSharedPreferences(TAG, Context.MODE_PRIVATE);
        boolean result = editor.getBoolean(isFirstTime, true);
        if (result) {
            dao.initData();
            editor.edit().putBoolean(isFirstTime, false).apply();
        }
        setAdapter();
    }

    private void setAdapter() {
        Observable
                .create(new ObservableOnSubscribe<List<MediaChannelBean>>() {
                    @Override
                    public void subscribe(@NonNull ObservableEmitter<List<MediaChannelBean>> e) throws Exception {
                        list = dao.queryAll();
                        e.onNext(list);
                    }
                })
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .compose(this.<List<MediaChannelBean>>bindUntilEvent(FragmentEvent.DESTROY))
                .subscribe(new Consumer<List<MediaChannelBean>>() {
                    @Override
                    public void accept(@NonNull List<MediaChannelBean> list) throws Exception {
                        adapter.setItems(list);
                        adapter.notifyDataSetChanged();
                        if (list.size() == 0) {
                            tv_desc.setVisibility(View.VISIBLE);
                        } else {
                            tv_desc.setVisibility(View.GONE);
                        }
                    }
                });
    }

    private void initView(View view) {
        recyclerView = view.findViewById(recycler_view);
        recyclerView.setHasFixedSize(true);
        recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));

        swipeRefreshLayout = view.findViewById(R.id.refresh_layout);
        swipeRefreshLayout.setColorSchemeColors(SettingUtil.getInstance().getColor());
        swipeRefreshLayout.setOnRefreshListener(this);
        tv_desc = view.findViewById(R.id.tv_desc);

        IOnItemLongClickListener listener = new IOnItemLongClickListener() {
            @Override
            public void onLongClick(View view, int position) {
                final MediaChannelBean item = list.get(position);
                AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
                builder.setMessage("取消订阅\\" " + item.getName() + " \\"?");
                builder.setPositiveButton(R.string.button_enter, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        new Thread(new Runnable() {
                            @Override
                            public void run() {
                                dao.delete(item.getId());
                                setAdapter();
                            }
                        }).start();
                        dialog.dismiss();
                    }
                });
                builder.setNegativeButton(R.string.button_cancel, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        dialog.dismiss();
                    }
                });
                builder.show();
            }
        };
        adapter = new MultiTypeAdapter();
        Register.registerMediaChannelItem(adapter, listener);
        recyclerView.setAdapter(adapter);
    }

    @Override
    public void onRefresh() {
        swipeRefreshLayout.setRefreshing(true);
        setAdapter();
        swipeRefreshLayout.setRefreshing(false);
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        if (instance != null) {
            instance = null;
        }
    }
}

   注意:如果setAdapter简化成下面这个函数,直观效果一直。 

private void setAdapter(){
        list=dao.queryAll();
        adapter.setItems(list);
        adapter.notifyDataSetChanged();
        if (list.size() == 0) {
            tv_desc.setVisibility(View.VISIBLE);
        } else {
            tv_desc.setVisibility(View.GONE);
        }
    }

  但是,如果订阅号的数量很多很多后,这种效果远远不如订阅的方法性能好。

  所以我们统一就用订阅的方式吧。

  而且更加重要的是,如果没有数据的时候,

  这里要进行界面操作,如果直接在io线程会发生异常的。

 

2.4.发现Register中还没有注册类型以及传入监听求

 public static void registerMediaChannelItem(@NonNull MultiTypeAdapter adapter, @NonNull IOnItemLongClickListener listener) {
        adapter.register(MediaChannelBean.class, new MediaChannelViewBinder(listener));
    }

  这里发现了监听器传进去了,说明绑定类中要给每一行都要设置这个listener

  然后这里又看到MediaChannelViewBinder绑定类还没有实现呢!

  第三点主要讲解这个简单的绑定类。

 


3.订阅号视图绑定类

3.1.首先看一下视图布局吧。==>item_media_channel.xml 

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@color/viewBackground">

    <LinearLayout
        android:id="@+id/content"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="?attr/selectableItemBackground"
        android:foreground="?attr/selectableItemBackground"
        android:padding="8dp">

        <com.meiji.toutiao.widget.CircleImageView
            android:id="@+id/cv_avatar"
            android:layout_width="52dp"
            android:layout_height="52dp"
            android:layout_gravity="center"
            android:scaleType="centerCrop"
            android:src="@color/textColorPrimary"/>

        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:layout_marginLeft="8dp"
            android:layout_marginStart="8dp">

            <TextView
                android:id="@+id/tv_mediaName"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentLeft="true"
                android:layout_alignParentStart="true"
                android:layout_toLeftOf="@+id/tv_followCount"
                android:layout_toStartOf="@+id/tv_followCount"
                android:maxLines="1"
                android:textSize="16sp"
                android:textStyle="bold"
                tools:text="新华国际"/>

            <TextView
                android:id="@+id/tv_followCount"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentEnd="true"
                android:layout_alignParentRight="true"
                android:text=""
                tools:text="111人关注"/>

            <TextView
                android:id="@+id/tv_descText"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_below="@+id/tv_mediaName"
                android:layout_marginTop="4dp"
                android:ellipsize="end"
                android:maxLines="1"
                android:textSize="14sp"
                tools:text="中国军力超越日本 日本为什么不怕中国?普京一句话让国人顿悟"/>
        </RelativeLayout>

    </LinearLayout>

    <View
        android:id="@+id/divider"
        android:layout_width="match_parent"
        android:layout_height="1px"
        android:layout_below="@+id/content"
        android:background="@color/line_divider"/>

</RelativeLayout>

  视图效果预览:

  

 

 

3.2.然后就是这个绑定类了==>MediaChannelViewBinder 

package com.jasonjan.headnews.binder.media;

import android.content.Context;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import com.jakewharton.rxbinding2.view.RxView;
import com.jasonjan.headnews.R;
import com.jasonjan.headnews.bean.media.MediaChannelBean;
import com.jasonjan.headnews.interfaces.IOnItemLongClickListener;
import com.jasonjan.headnews.main.ErrorAction;
import com.jasonjan.headnews.util.ImageLoader;
import com.jasonjan.headnews.widget.CircleImageView;

import java.util.concurrent.TimeUnit;

import io.reactivex.functions.Consumer;
import me.drakeet.multitype.ItemViewBinder;

/**
 * Created by JasonJan on 2017/12/14.
 */

public class MediaChannelViewBinder extends ItemViewBinder<MediaChannelBean,MediaChannelViewBinder.ViewHolder> {

    private IOnItemLongClickListener listener;

    public MediaChannelViewBinder(IOnItemLongClickListener listener) {
        this.listener = listener;
    }

    @NonNull
    @Override
    protected MediaChannelViewBinder.ViewHolder onCreateViewHolder(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent) {
        View view = inflater.inflate(R.layout.item_media_channel, parent, false);
        return new ViewHolder(view, listener);
    }

    @Override
    protected void onBindViewHolder(@NonNull final ViewHolder holder, @NonNull final MediaChannelBean item){
        try {
            final Context context = holder.itemView.getContext();
            String url = item.getAvatar();
            ImageLoader.loadCenterCrop(context, url, holder.cv_avatar, R.color.viewBackground);
            holder.tv_mediaName.setText(item.getName());
            holder.tv_descText.setText(item.getDescText());

            RxView.clicks(holder.itemView)
                    .throttleFirst(1, TimeUnit.SECONDS)
                    .subscribe(new Consumer<Object>() {
                        @Override
                        public void accept(@io.reactivex.annotations.NonNull Object o) throws Exception {
                           // MediaHomeActivity.launch(item.getId());
                        }
                    });
        } catch (Exception e) {
            ErrorAction.print(e);
        }
    }

    public class ViewHolder extends RecyclerView.ViewHolder implements View.OnLongClickListener {

        private CircleImageView cv_avatar;
        private TextView tv_mediaName;
        private TextView tv_followCount;
        private TextView tv_descText;
        private IOnItemLongClickListener listener;

        public ViewHolder(View itemView, IOnItemLongClickListener listener) {
            super(itemView);
            this.cv_avatar = itemView.findViewById(R.id.cv_avatar);
            this.tv_mediaName = itemView.findViewById(R.id.tv_mediaName);
            this.tv_followCount = itemView.findViewById(R.id.tv_followCount);
            this.tv_descText = itemView.findViewById(R.id.tv_descText);
            this.listener = listener;
            itemView.setOnLongClickListener(this);
        }

        @Override
        public boolean onLongClick(View v) {
            if (listener != null) {
                listener.onLongClick(v, getLayoutPosition());
                return true;
            }
            return false;
        }
    }
}

  这里先理一理RxView.clicks思路。

  这个ViewHolder继承于RecyclerView.ViewHolder

  绑定类由于继承ItemViewBinder,不得不去实现onBindViewHolder<T,这里面的ViewHolder>

  所以在这里面处理ViewHolder的holdr.itemView的时候

  要用到RxView.clicks(view)

  如下方的代码:

RxView.clicks(holder.itemView)
                    .throttleFirst(1, TimeUnit.SECONDS)
                    .subscribe(new Consumer<Object>() {
                        @Override
                        public void accept(@io.reactivex.annotations.NonNull Object o) throws Exception {
                            MediaHomeActivity.launch(item.getId());
                        }
                    });

  RxView代表着用了第三方库,结合了RxJava。

  采用订阅的方式处理点击事件。

  当然也可以不用这种方式,不过我还没发现这种方式的好处。

  这里的ThrottleFirst操作符会定期发射这个时间段里源Observable发射的第一个数据。

  参考博客:RxJava操作符(三)


4.效果预览

4.1.目前完成的工作

  新闻的主页面三种大类型

  图片的一种大类型(也只用了一种)

  视频的一种大类型(采用了新闻主页面的其中一种)

  订阅号的主页面的一种大类型(也只采用了一种)

  然后还有一些点击事件,调转到相应的活动页面还未实现。

 

 

4.2.目前手机真实数据效果

  

 



以上是关于TouTiao开源项目 分析笔记13 最后一个订阅号的实现主页面的主要内容,如果未能解决你的问题,请参考以下文章

TouTiao开源项目 分析笔记3

TouTiao开源项目 分析笔记19 问答内容

TouTiao开源项目 分析笔记4

TouTiao开源项目 分析笔记7

TouTiao开源项目 分析笔记14 段子评论

TouTiao开源项目 分析笔记9 实现一个问答主页面