zoukankan      html  css  js  c++  java
  • 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.目前手机真实数据效果

      



    既然选择了,便不顾风雨兼程。Just follow yourself.
  • 相关阅读:
    Noe4j启动警告
    SpringBoot
    MySQL数据库 java SQL语句区分大小写分析
    day24 模块的语法
    day23 re模块
    day22 常用模块02 序列化
    day21 常用模块01
    day20 面向对象06 MRO和C3算法
    day19 面向对象05 约束
    day18 面向对象04 反射
  • 原文地址:https://www.cnblogs.com/Jason-Jan/p/8039075.html
Copyright © 2011-2022 走看看