TouTiao开源项目 分析笔记11 以总体到局部的思路 构建图片主列表
1.构建图片主列表的整体片段PhotoTabLayout
1.1.首先创建一个PhotoTabLayout片段
public class PhotoTabLayout extends Fragment { private static final String TAG = "PhotoTabLayout"; private static PhotoTabLayout instance = null; private static int pageSize = InitApp.AppContext.getResources().getStringArray(R.array.photo_id).length; private String categoryId[] = InitApp.AppContext.getResources().getStringArray(R.array.photo_id); private String categoryName[] = InitApp.AppContext.getResources().getStringArray(R.array.photo_name); private TabLayout tabLayout; private ViewPager viewPager; private List<Fragment> fragmentList = new ArrayList<>(); private BasePagerAdapter adapter; public static PhotoTabLayout getInstance() { if (instance == null) { instance = new PhotoTabLayout(); } return instance; } @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_photo_tab, container, false); initView(view); initData(); return view; } @Override public void onResume() { super.onResume(); tabLayout.setBackgroundColor(SettingUtil.getInstance().getColor()); } private void initView(View view) { tabLayout = view.findViewById(R.id.tab_layout_photo); viewPager = view.findViewById(R.id.view_pager_photo); tabLayout.setupWithViewPager(viewPager); tabLayout.setTabMode(TabLayout.MODE_SCROLLABLE); tabLayout.setBackgroundColor(SettingUtil.getInstance().getColor()); viewPager.setOffscreenPageLimit(pageSize); } private void initData() { for (int i = 0; i < categoryId.length; i++) { Fragment fragment = PhotoArticleView.newInstance(categoryId[i]); fragmentList.add(fragment); } adapter = new BasePagerAdapter(getChildFragmentManager(), fragmentList, categoryName); viewPager.setAdapter(adapter); } public void onDoubleClick() { if (fragmentList != null && fragmentList.size() > 0) { int item = viewPager.getCurrentItem(); ((BaseListFragment) fragmentList.get(item)).onRefresh(); } } @Override public void onDestroy() { if (instance != null) { instance = null; } super.onDestroy(); } }
1.2.这个片段需要一个布局fragment_photo_tab.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" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <android.support.design.widget.TabLayout android:id="@+id/tab_layout_photo" style="@style/TabLayout" android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="?attr/actionBarSize" android:theme="@style/AppTheme.AppBarOverlay" app:tabTextColor="@color/gray"> </android.support.design.widget.TabLayout> <android.support.v4.view.ViewPager android:id="@+id/view_pager_photo" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior" tools:layout="@layout/fragment_list"> </android.support.v4.view.ViewPager> </LinearLayout>
1.3.布局真实效果预览
1.4.然后就是tabLayout的静态文字
在资源文件中values中的photo_category.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <string-array name="photo_name"> <item>全部</item> <item>老照片</item> <item>故事照片</item> <item>摄影集</item> </string-array> <string-array name="photo_id"> <item>组图</item> <item>gallery_old_picture</item> <item>gallery_story</item> <item>gallery_photograthy</item> </string-array> </resources>
1.5.然后就是将图片主列表的4个小type用一个BasePagerAdapter联系起来
下面的代码在上方的PhotoTabLayout.java中的一个函数中,这里再拿出来讲一下。
private void initData() { for (int i = 0; i < categoryId.length; i++) { //这里用了PhotoArticleView,图片中所有fragment都是用这个类,所以要新建这个类了,下面再详细讲解这个类的构造过程 Fragment fragment = PhotoArticleView.newInstance(categoryId[i]); fragmentList.add(fragment); } //这里就是将文字和片段统一的过程了,最重要的就是这个BasePagerAdapter将所有片段集合,设置到viewPager的适配器中 adapter = new BasePagerAdapter(getChildFragmentManager(), fragmentList, categoryName); viewPager.setAdapter(adapter); }
1.6.看一下这个片段的适配器吧
名字叫做BasePagerAdapter
public class BasePagerAdapter extends FragmentStatePagerAdapter { private List<Fragment> fragmentList; private List<String> titleList; public BasePagerAdapter(FragmentManager fm, List<Fragment> fragmentList, String[] titles) { super(fm); this.fragmentList = fragmentList; this.titleList = new ArrayList<>(Arrays.asList(titles)); } public BasePagerAdapter(FragmentManager fm, List<Fragment> fragmentList, List<String> titleList) { super(fm); this.fragmentList = fragmentList; this.titleList = titleList; } @Override public Fragment getItem(int position) { return fragmentList.get(position); } @Override public int getCount() { return titleList.size(); } @Override public CharSequence getPageTitle(int position) { return titleList.get(position); } @Override public int getItemPosition(Object object) { return PagerAdapter.POSITION_NONE; } public void recreateItems(List<Fragment> fragmentList, List<String> titleList) { this.fragmentList = fragmentList; this.titleList = titleList; notifyDataSetChanged(); } }
1.7.第一阶段结束,主要完成了PhotoTabLayout这个图片主页面的布局和相应的逻辑。
第二阶段主要处理4个分片段的布局和逻辑了。
2.图片系列的文章的基础Bean类
2.1.第一集团数据返回==>调用API返回的数据
/** * has_more : true * message : success * next : {"max_behot_time":1494154227} * data:[[.....]] */
2.2.第二集团数据返回==>上面的data返回的数据
//data返回的数据 /** * image_url : http://p9.pstatp.com/list/640x360/1b820001767b34813c82 * media_avatar_url : http://p1.pstatp.com/large/d2a0011291a5cfef5c3 * article_genre : gallery * is_diversion_page : false * title : 我拍少女签约模特王纯粹少女视觉 * middle_mode : false * gallary_image_count : 41 * image_list : [{"url":"http://p3.pstatp.com/list/640x360/1b820001767b34813c82","width":620,"url_list":[{"url":"http://p9.pstatp.com/list/640x360/1b820001767b34813c82"},{"url":"http://pb1.pstatp.com/list/640x360/1b820001767b34813c82"},{"url":"http://pb3.pstatp.com/list/640x360/1b820001767b34813c82"}],"uri":"list/640x360/1b820001767b34813c82","height":855},{"url":"http://p3.pstatp.com/list/640x360/1b8700015c172e7de742","width":1280,"url_list":[{"url":"http://p3.pstatp.com/list/1b8700015c172e7de742"},{"url":"http://pb9.pstatp.com/list/1b8700015c172e7de742"},{"url":"http://pb1.pstatp.com/list/1b8700015c172e7de742"}],"uri":"list/1b8700015c172e7de742","height":720},{"url":"http://p9.pstatp.com/list/640x360/1b8500015b6a1c897705","width":1656,"url_list":[{"url":"http://p9.pstatp.com/list/1b8500015b6a1c897705"},{"url":"http://pb1.pstatp.com/list/1b8500015b6a1c897705"},{"url":"http://pb3.pstatp.com/list/1b8500015b6a1c897705"}],"uri":"list/1b8500015b6a1c897705","height":2415}] * more_mode : true * behot_time : 1494158550 * source_url : /group/6408674663173767426/ * source : 我拍少女 * hot : 1 * is_feed_ad : false * has_gallery : true * single_mode : false * comments_count : 1 * group_id : 6408674663173767426 * media_url : http://toutiao.com/m4439122761/ * honey : true */
2.3.整个PhotoArticleBean类
public class PhotoArticleBean { /** * has_more : true * message : success * next : {"max_behot_time":1494154227} */ private boolean has_more; private String message; private NextBean next; private List<DataBean> data; public boolean isHas_more() { return has_more; } public void setHas_more(boolean has_more) { this.has_more = has_more; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public NextBean getNext() { return next; } public void setNext(NextBean next) { this.next = next; } public List<DataBean> getData() { return data; } public void setData(List<DataBean> data) { this.data = data; } public static class NextBean{ private int max_behot_time; public int getMax_behot_time(){ return max_behot_time; } public void setMax_behot_time(int max_behot_tim){ this.max_behot_time=max_behot_time; } } public static class DataBean implements Parcelable { public static final Creator<DataBean> CREATOR = new Creator<DataBean>() { @Override public DataBean createFromParcel(Parcel in) { return new DataBean(in); } @Override public DataBean[] newArray(int size) { return new DataBean[size]; } }; /** * image_url : http://p9.pstatp.com/list/640x360/1b820001767b34813c82 * media_avatar_url : http://p1.pstatp.com/large/d2a0011291a5cfef5c3 * article_genre : gallery * is_diversion_page : false * title : 我拍少女签约模特王纯粹少女视觉 * middle_mode : false * gallary_image_count : 41 * image_list : [{"url":"http://p3.pstatp.com/list/640x360/1b820001767b34813c82","width":620,"url_list":[{"url":"http://p9.pstatp.com/list/640x360/1b820001767b34813c82"},{"url":"http://pb1.pstatp.com/list/640x360/1b820001767b34813c82"},{"url":"http://pb3.pstatp.com/list/640x360/1b820001767b34813c82"}],"uri":"list/640x360/1b820001767b34813c82","height":855},{"url":"http://p3.pstatp.com/list/640x360/1b8700015c172e7de742","width":1280,"url_list":[{"url":"http://p3.pstatp.com/list/1b8700015c172e7de742"},{"url":"http://pb9.pstatp.com/list/1b8700015c172e7de742"},{"url":"http://pb1.pstatp.com/list/1b8700015c172e7de742"}],"uri":"list/1b8700015c172e7de742","height":720},{"url":"http://p9.pstatp.com/list/640x360/1b8500015b6a1c897705","width":1656,"url_list":[{"url":"http://p9.pstatp.com/list/1b8500015b6a1c897705"},{"url":"http://pb1.pstatp.com/list/1b8500015b6a1c897705"},{"url":"http://pb3.pstatp.com/list/1b8500015b6a1c897705"}],"uri":"list/1b8500015b6a1c897705","height":2415}] * more_mode : true * behot_time : 1494158550 * source_url : /group/6408674663173767426/ * source : 我拍少女 * hot : 1 * is_feed_ad : false * has_gallery : true * single_mode : false * comments_count : 1 * group_id : 6408674663173767426 * media_url : http://toutiao.com/m4439122761/ * honey : true */ private String image_url; private String media_avatar_url; private String article_genre; private boolean is_diversion_page; private String title; private boolean middle_mode; private int gallary_image_count; private boolean more_mode; private int behot_time; private String source_url; private String source; private int hot; private boolean is_feed_ad; private boolean has_gallery; private boolean single_mode; private int comments_count; private String group_id; private String media_url; private boolean honey; private List<ImageListBean> image_list; protected DataBean(Parcel in) { image_url = in.readString(); media_avatar_url = in.readString(); article_genre = in.readString(); is_diversion_page = in.readByte() != 0; title = in.readString(); middle_mode = in.readByte() != 0; gallary_image_count = in.readInt(); more_mode = in.readByte() != 0; behot_time = in.readInt(); source_url = in.readString(); source = in.readString(); hot = in.readInt(); is_feed_ad = in.readByte() != 0; has_gallery = in.readByte() != 0; single_mode = in.readByte() != 0; comments_count = in.readInt(); group_id = in.readString(); media_url = in.readString(); honey = in.readByte() != 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(image_url); dest.writeString(media_avatar_url); dest.writeString(article_genre); dest.writeByte((byte) (is_diversion_page ? 1 : 0)); dest.writeString(title); dest.writeByte((byte) (middle_mode ? 1 : 0)); dest.writeInt(gallary_image_count); dest.writeByte((byte) (more_mode ? 1 : 0)); dest.writeInt(behot_time); dest.writeString(source_url); dest.writeString(source); dest.writeInt(hot); dest.writeByte((byte) (is_feed_ad ? 1 : 0)); dest.writeByte((byte) (has_gallery ? 1 : 0)); dest.writeByte((byte) (single_mode ? 1 : 0)); dest.writeInt(comments_count); dest.writeString(group_id); dest.writeString(media_url); dest.writeByte((byte) (honey ? 1 : 0)); } @Override public int describeContents() { return 0; } public String getImage_url() { return image_url; } public void setImage_url(String image_url) { this.image_url = image_url; } public String getMedia_avatar_url() { return media_avatar_url; } public void setMedia_avatar_url(String media_avatar_url) { this.media_avatar_url = media_avatar_url; } public String getArticle_genre() { return article_genre; } public void setArticle_genre(String article_genre) { this.article_genre = article_genre; } public boolean isIs_diversion_page() { return is_diversion_page; } public void setIs_diversion_page(boolean is_diversion_page) { this.is_diversion_page = is_diversion_page; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public boolean isMiddle_mode() { return middle_mode; } public void setMiddle_mode(boolean middle_mode) { this.middle_mode = middle_mode; } public int getGallary_image_count() { return gallary_image_count; } public void setGallary_image_count(int gallary_image_count) { this.gallary_image_count = gallary_image_count; } public boolean isMore_mode() { return more_mode; } public void setMore_mode(boolean more_mode) { this.more_mode = more_mode; } public int getBehot_time() { return behot_time; } public void setBehot_time(int behot_time) { this.behot_time = behot_time; } public String getSource_url() { return source_url; } public void setSource_url(String source_url) { this.source_url = source_url; } public String getSource() { return source; } public void setSource(String source) { this.source = source; } public int getHot() { return hot; } public void setHot(int hot) { this.hot = hot; } public boolean isIs_feed_ad() { return is_feed_ad; } public void setIs_feed_ad(boolean is_feed_ad) { this.is_feed_ad = is_feed_ad; } public boolean isHas_gallery() { return has_gallery; } public void setHas_gallery(boolean has_gallery) { this.has_gallery = has_gallery; } public boolean isSingle_mode() { return single_mode; } public void setSingle_mode(boolean single_mode) { this.single_mode = single_mode; } public int getComments_count() { return comments_count; } public void setComments_count(int comments_count) { this.comments_count = comments_count; } public String getGroup_id() { return group_id; } public void setGroup_id(String group_id) { this.group_id = group_id; } public String getMedia_url() { return media_url; } public void setMedia_url(String media_url) { this.media_url = media_url; } public boolean isHoney() { return honey; } public void setHoney(boolean honey) { this.honey = honey; } public List<ImageListBean> getImage_list() { return image_list; } public void setImage_list(List<ImageListBean> image_list) { this.image_list = image_list; } public static class ImageListBean { /** * url : http://p3.pstatp.com/list/640x360/1b820001767b34813c82 * width : 620 * url_list : [{"url":"http://p9.pstatp.com/list/640x360/1b820001767b34813c82"},{"url":"http://pb1.pstatp.com/list/640x360/1b820001767b34813c82"},{"url":"http://pb3.pstatp.com/list/640x360/1b820001767b34813c82"}] * uri : list/640x360/1b820001767b34813c82 * height : 855 */ private String url; private int width; private String uri; private int height; private List<UrlListBean> url_list; public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public int getWidth() { return width; } public void setWidth(int width) { this.width = width; } public String getUri() { return uri; } public void setUri(String uri) { this.uri = uri; } public int getHeight() { return height; } public void setHeight(int height) { this.height = height; } public List<UrlListBean> getUrl_list() { return url_list; } public void setUrl_list(List<UrlListBean> url_list) { this.url_list = url_list; } public static class UrlListBean { /** * url : http://p9.pstatp.com/list/640x360/1b820001767b34813c82 */ private String url; public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } } } } }
3.图片系列的视图以及处理器控制
3.1.最底层视图以及处理器接口
interface IPhotoArticle { interface View extends IBaseListView<Presenter> { /** * 请求数据 */ void onLoadData(); } interface Presenter extends IBasePresenter { /** * 请求数据 */ void doLoadData(String... category); /** * 再起请求数据 */ void doLoadMoreData(); /** * 设置适配器 */ void doSetAdapter(List<PhotoArticleBean.DataBean> dataBeen); void doShowNoMore(); } }
3.2.图片系列的视图
package com.jasonjan.headnews.module.photo; import android.os.Bundle; import android.view.View; import com.jasonjan.headnews.adapter.DiffCallback; import com.jasonjan.headnews.bean.common.LoadingBean; import com.jasonjan.headnews.main.Register; import com.jasonjan.headnews.module.base.BaseListFragment; import com.jasonjan.headnews.util.OnLoadMoreListener; import java.util.List; import me.drakeet.multitype.Items; import me.drakeet.multitype.MultiTypeAdapter; /** * Created by JasonJan on 2017/12/13. */ public class PhotoArticleView extends BaseListFragment<IPhotoArticle.Presenter> implements IPhotoArticle.View { private static final String TAG = "PhotoArticleView"; private String categoryId; public static PhotoArticleView newInstance(String categoryId){ Bundle bundle=new Bundle(); bundle.putString(TAG,categoryId); PhotoArticleView instance=new PhotoArticleView(); instance.setArguments(bundle); return instance; } @Override protected void initData(){ categoryId=getArguments().getString(TAG); } @Override protected void initView(View view) { super.initView(view); adapter = new MultiTypeAdapter(oldItems); Register.registerPhotoArticleItem(adapter); recyclerView.setAdapter(adapter); recyclerView.addOnScrollListener(new OnLoadMoreListener() { @Override public void onLoadMore() { if (canLoadMore) { canLoadMore = false; presenter.doLoadMoreData(); } } }); } @Override public void onLoadData() { onShowLoading(); presenter.doLoadData(categoryId); } @Override public void onSetAdapter(final List<?> list) { Items newItems = new Items(list); newItems.add(new LoadingBean()); DiffCallback.notifyDataSetChanged(oldItems, newItems, DiffCallback.PHOTO, adapter); oldItems.clear(); oldItems.addAll(newItems); canLoadMore = true; } @Override public void setPresenter(IPhotoArticle.Presenter presenter) { if (null == presenter) { this.presenter = new PhotoArticlePresenter(this); } } }
然后发现处理器还没有设置呢。
然后还有视图绑定也未实现(在自定义的Register中实现)
然后还有处理新老数据来刷新(在自定义的Diffback中实现)
3.3.图片系列的处理器
package com.jasonjan.headnews.module.photo; import com.jasonjan.headnews.bean.photo.PhotoArticleBean; import com.jasonjan.headnews.main.ErrorAction; import com.jasonjan.headnews.main.RetrofitFactory; import com.jasonjan.headnews.util.TimeUtil; import java.util.ArrayList; import java.util.List; import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.annotations.NonNull; import io.reactivex.functions.Consumer; import io.reactivex.functions.Function; import io.reactivex.functions.Predicate; import io.reactivex.schedulers.Schedulers; /** * Created by JasonJan on 2017/12/13. */ public class PhotoArticlePresenter implements IPhotoArticle.Presenter { private static final String TAG="PhotoArticlePresenter"; private IPhotoArticle.View view; private List<PhotoArticleBean.DataBean> dataList=new ArrayList<>(); private String category; private String time; PhotoArticlePresenter(IPhotoArticle.View view) { this.view = view; this.time = TimeUtil.getCurrentTimeStamp(); } @Override public void doLoadData(String... category){ try { if (null == this.category) { this.category = category[0]; } } catch (Exception e) { ErrorAction.print(e); } // 释放内存 if (dataList.size() > 100) { dataList.clear(); } RetrofitFactory.getRetrofit().create(IPhotoApi.class).getPhotoArticle(this.category, time) .subscribeOn(Schedulers.io()) .switchMap(new Function<PhotoArticleBean, Observable<PhotoArticleBean.DataBean>>() { @Override public Observable<PhotoArticleBean.DataBean> apply(@NonNull PhotoArticleBean photoArticleBean) throws Exception { List<PhotoArticleBean.DataBean> data = photoArticleBean.getData(); // 移除最后一项 数据有重复 if (data.size() > 0) data.remove(data.size() - 1); //time = photoArticleBean.getNext().getMax_behot_time()+""; return Observable.fromIterable(data); } }) .filter(new Predicate<PhotoArticleBean.DataBean>() { @Override public boolean test(@NonNull PhotoArticleBean.DataBean dataBean) throws Exception { // 去除重复新闻 for (PhotoArticleBean.DataBean bean : dataList) { if (dataBean.getTitle().equals(bean.getTitle())) { return false; } } return true; } }) .toList() .compose(view.<List<PhotoArticleBean.DataBean>>bindToLife()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Consumer<List<PhotoArticleBean.DataBean>>() { @Override public void accept(@NonNull List<PhotoArticleBean.DataBean> list) throws Exception { if (null != list && list.size() > 0) { doSetAdapter(list); } else { doShowNoMore(); } } }, new Consumer<Throwable>() { @Override public void accept(@NonNull Throwable throwable) throws Exception { doShowNetError(); ErrorAction.print(throwable); } }); } @Override public void doLoadMoreData(){ doLoadData(); } @Override public void doSetAdapter(List<PhotoArticleBean.DataBean> dataBean){ dataList.addAll(dataBean); view.onSetAdapter(dataList); view.onHideLoading(); } @Override public void doRefresh() { if (dataList.size() != 0) { dataList.clear(); time = TimeUtil.getCurrentTimeStamp()+""; } view.onShowLoading(); doLoadData(); } @Override public void doShowNetError() { view.onHideLoading(); view.onShowNetError(); } @Override public void doShowNoMore() { view.onHideLoading(); view.onShowNoMore(); } }
3.4.图片系列文章的API接口请求
public interface IPhotoApi { /** * 获取图片标题等信息 * http://www.toutiao.com/api/article/feed/?category=类型&as=A115C8457F69B85&cp=585F294B8845EE1&_=时间&count=30 */ @GET("http://www.toutiao.com/api/pc/feed/?as=A1C598BB87BE7DA&cp=58B72ED7AD3A0E1") Observable<PhotoArticleBean> getPhotoArticle( @Query("category") String category, @Query("max_behot_time") String maxBehotTime); /** * 获取图片内容HTML内容 * 抓取 url 较复杂 * 详情查看 {@linkplain com.meiji.toutiao.module.photo.content.PhotoContentPresenter#doLoadData(String...)} */ @GET() @Headers("User-Agent:" + Constant.USER_AGENT_PC) Call<ResponseBody> getPhotoContentHTML(@Url String url); }
3.5.然后是Register类中声明3中Binder
public static void registerPhotoArticleItem(@NonNull MultiTypeAdapter adapter) { adapter.register(PhotoArticleBean.DataBean.class, new PhotoArticleViewBinder()); adapter.register(LoadingBean.class, new LoadingViewBinder()); adapter.register(LoadingEndBean.class, new LoadingEndViewBinder()); }
3.6.然后在DiffCallback中加这个类型吧
在areItemsTheSame函数中添加Photo类型。
case PHOTO: return ((PhotoArticleBean.DataBean) oldList.get(oldItemPosition)).getTitle().equals( ((PhotoArticleBean.DataBean) newList.get(newItemPosition)).getTitle());
在areContentsTheSame中添加Photo类型
case PHOTO: return ((PhotoArticleBean.DataBean) oldList.get(oldItemPosition)).getSource_url().equals( ((PhotoArticleBean.DataBean) newList.get(newItemPosition)).getSource_url());
4.视图绑定类PhotoArticleViewBinder
4.1.视图绑定类的布局定义
<?xml version="1.0" encoding="utf-8"?> <android.support.v7.widget.CardView 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_marginBottom="4dp" android:layout_marginTop="4dp" android:background="@color/viewBackground" app:cardElevation="1dp"> <RelativeLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="?attr/selectableItemBackground" android:foreground="?attr/selectableItemBackground" android:padding="16dp"> <LinearLayout android:id="@+id/header" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_vertical" android:orientation="horizontal"> <com.meiji.toutiao.widget.CircleImageView android:id="@+id/iv_media" android:layout_width="22dp" android:layout_height="22dp" android:scaleType="centerCrop"/> <TextView android:id="@+id/tv_extra" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="8dp" android:layout_marginStart="8dp" android:ellipsize="end" android:maxLength="30" android:maxLines="1" android:textAppearance="@style/TextAppearance.AppCompat.Caption" tools:text="新闻源 - 1小时前"/> <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <ImageView android:id="@+id/iv_dots" android:layout_width="22dp" android:layout_height="22dp" android:layout_alignParentEnd="true" android:layout_alignParentRight="true" android:layout_centerVertical="true" android:padding="4dp" android:scaleType="center" app:srcCompat="@drawable/ic_dots_horizontal_grey500_24dp" tools:ignore="ContentDescription"/> </RelativeLayout> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@+id/header" android:layout_marginTop="4dp" android:orientation="vertical"> <TextView android:id="@+id/tv_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:maxLines="1" android:textStyle="bold" tools:text="看完王健林的私人飞机,再看刘强东的,刚好相差1个小目标的钱"/> <LinearLayout android:layout_width="match_parent" android:layout_height="82dp" android:layout_marginTop="4dp" android:orientation="horizontal"> <ImageView android:id="@+id/iv_0" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginEnd="4dp" android:layout_marginRight="4dp" android:layout_weight="1" android:src="@color/viewBackground" tools:ignore="ContentDescription"/> <ImageView android:id="@+id/iv_1" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginEnd="4dp" android:layout_marginRight="4dp" android:layout_weight="1" android:src="@color/viewBackground" tools:ignore="ContentDescription"/> <ImageView android:id="@+id/iv_2" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="1" android:src="@color/viewBackground" tools:ignore="ContentDescription"/> </LinearLayout> </LinearLayout> </RelativeLayout> </android.support.v7.widget.CardView>
4.2.视图效果
4.3.视图绑定类
public class PhotoArticleViewBinder extends ItemViewBinder<PhotoArticleBean.DataBean, PhotoArticleViewBinder.ViewHolder> { @NonNull @Override protected PhotoArticleViewBinder.ViewHolder onCreateViewHolder(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent) { View view = inflater.inflate(R.layout.item_photo_article, parent, false); return new ViewHolder(view); } @Override protected void onBindViewHolder(@NonNull final ViewHolder holder, @NonNull final PhotoArticleBean.DataBean item) { final Context context = holder.itemView.getContext(); try { String tv_title = item.getTitle(); if (!TextUtils.isEmpty(item.getMedia_avatar_url())) { ImageLoader.loadCenterCrop(context, item.getMedia_avatar_url().replaceFirst("//","http://").trim(), holder.iv_media, R.color.viewBackground,R.mipmap.error_image); } if (item.getImage_list() != null) { int size = item.getImage_list().size(); String[] ivs = new String[size]; for (int i = 0; i < item.getImage_list().size(); i++) { ivs[i] = item.getImage_list().get(i).getUrl().replaceFirst("//","http://").trim(); } switch (ivs.length) { case 1: ImageLoader.loadCenterCrop(context, ivs[0], holder.iv_0, R.color.viewBackground,R.mipmap.error_image); break; case 2: ImageLoader.loadCenterCrop(context, ivs[0], holder.iv_0, R.color.viewBackground,R.mipmap.error_image); ImageLoader.loadCenterCrop(context, ivs[1], holder.iv_1, R.color.viewBackground,R.mipmap.error_image); break; case 3: ImageLoader.loadCenterCrop(context, ivs[0], holder.iv_0, R.color.viewBackground,R.mipmap.error_image); ImageLoader.loadCenterCrop(context, ivs[1], holder.iv_1, R.color.viewBackground,R.mipmap.error_image); ImageLoader.loadCenterCrop(context, ivs[2], holder.iv_2, R.color.viewBackground,R.mipmap.error_image); break; } Log.i("图片绑定类:","\n头像地址="+item.getMedia_avatar_url().replaceFirst("//","")+"\n图片链接="+ivs[0]+"\n"+ivs[1]+"\n"+ivs[2]); } String tv_source = item.getSource(); String tv_datetime = item.getBehot_time() + ""; String comments_count = item.getComments_count() + "评论"; if (!TextUtils.isEmpty(tv_datetime)) { tv_datetime = TimeUtil.getTimeStampAgo(tv_datetime); } holder.tv_title.setText(tv_title); holder.tv_title.setTextSize(SettingUtil.getInstance().getTextSize()); holder.tv_extra.setText(tv_source + " - " + comments_count + " - " + tv_datetime); holder.iv_dots.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { PopupMenu popupMenu = new PopupMenu(context, holder.iv_dots, Gravity.END, 0, R.style.MyPopupMenu); popupMenu.inflate(R.menu.menu_share); popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem menu) { int itemId = menu.getItemId(); if (itemId == R.id.action_share) { String shareUrl = item.getSource_url(); if (!shareUrl.contains("toutiao")) { shareUrl = "http://toutiao.com" + shareUrl; } IntentAction.send(context, item.getTitle() + "\n" + shareUrl); } return false; } }); popupMenu.show(); } }); RxView.clicks(holder.itemView) .throttleFirst(1, TimeUnit.SECONDS) .subscribe(new Consumer<Object>() { @Override public void accept(@io.reactivex.annotations.NonNull Object o) throws Exception { PhotoContentActivity.launch(item); } }); } catch (Exception e) { ErrorAction.print(e); } } public class ViewHolder extends RecyclerView.ViewHolder { private CircleImageView iv_media; private TextView tv_extra; private TextView tv_title; private ImageView iv_0; private ImageView iv_1; private ImageView iv_2; private ImageView iv_dots; public ViewHolder(View itemView) { super(itemView); this.iv_media = itemView.findViewById(R.id.iv_media); this.tv_extra = itemView.findViewById(R.id.tv_extra); this.tv_title = itemView.findViewById(R.id.tv_title); this.iv_0 = itemView.findViewById(R.id.iv_0); this.iv_1 = itemView.findViewById(R.id.iv_1); this.iv_2 = itemView.findViewById(R.id.iv_2); this.iv_dots = itemView.findViewById(R.id.iv_dots); } } }
5.目前效果预览
5.1.目前完成的工作
新闻的三种大类型
图片的一种大类型,这种大类型有4个分类,为全部、老照片、故事照片和摄影集。
但是每种类型的评论以及详细页面还未实现。
因为图片调用今日头条的API不稳定,有时候可以获得数据,有时候无法获得。
所以这里是无法确保每次刷新都有新的数据。
5.2.手机真实数据预览