OSCHINA客户端完全剖析(二)

接上期,着重分析主干逻辑,先看第一个综合页面。

综合

五个Tab页定义可以看MainTab ,虽然enum占用存储较多,但可读性的提升也同样显而易见,以下是代码片段:

public enum MainTab {

    NEWS(0, R.string.main_tab_name_news, R.drawable.tab_icon_new,
            NewsViewPagerFragment.class),

    TWEET(1, R.string.main_tab_name_tweet, R.drawable.tab_icon_tweet,
            TweetsViewPagerFragment.class),

    QUICK(2, R.string.main_tab_name_quick, R.drawable.tab_icon_new,
            null),

    EXPLORE(3, R.string.main_tab_name_explore, R.drawable.tab_icon_explore,
            ExploreFragment.class),

    ME(4, R.string.main_tab_name_my, R.drawable.tab_icon_me,
            MyInformationFragment.class);

综合页当然就是NewsViewPagerFragment了,我们自顶向下逐步分析。

BaseFragment extends Fragment

预定义了多种状态,有可能会在子类中用到:

public static final int STATE_NONE = 0;
    public static final int STATE_REFRESH = 1;
    public static final int STATE_LOADMORE = 2;
    public static final int STATE_NOMORE = 3;
    public static final int STATE_PRESSNONE = 4;// 正在下拉但还没有到刷新的状态
    public static int mState = STATE_NONE;

主要是调用Activity,提供ProgressDialog显隐功能:

protected void hideWaitDialog() {
        FragmentActivity activity = getActivity();
        if (activity instanceof DialogControl) {
            ((DialogControl) activity).hideWaitDialog();
        }
    }

    protected ProgressDialog showWaitDialog() {
        return showWaitDialog(R.string.loading);
    }

BaseViewPagerFragment extends BaseFragment

官方描述:带有导航条的基类。
这一点从其持有的域也可以看出:

protected PagerSlidingTabStrip mTabStrip;
    protected ViewPager mViewPager;
    protected ViewPageFragmentAdapter mTabsAdapter;
    protected EmptyLayout mErrorLayout;

onCreateView()中加载以下布局:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="fill_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <!-- 导航标题栏 -->
    <net.oschina.app.widget.PagerSlidingTabStrip
        android:id="@+id/pager_tabstrip"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="?attr/windows_bg"
        app:allowWidthFull="true"
        app:slidingBlock="?attr/sliding_block_bg" >
    </net.oschina.app.widget.PagerSlidingTabStrip>

    <View
        android:id="@+id/view_pager_line"
        android:layout_width="match_parent"
        android:layout_below="@id/pager_tabstrip"
        android:layout_height="1px"
        android:background="?attr/lineColor" />

    <android.support.v4.view.ViewPager
        android:id="@+id/pager"
        style="@style/fill_fill"
        android:layout_below="@id/view_pager_line">
    </android.support.v4.view.ViewPager>

    <net.oschina.app.ui.empty.EmptyLayout
        android:id="@+id/error_layout"
        style="@style/fill_fill"
        android:visibility="gone" />

</RelativeLayout>

其中PagerSlidingTabStrip extends HorizontalScrollView,同样的一个效果,还可以参见Google Contacts源码中在PeopleActivity使用的ViewPagerTabs extends HorizontalScrollView:
http://androidxref.com/6.0.1_r10/xref/packages/apps/ContactsCommon/src/com/android/contacts/common/list/ViewPagerTabs.java

onViewCreated()中调用了如下两行代码,不用说,当然是留给子类覆盖的了:

        setScreenPageLimit();
        onSetupTabAdapter(mTabsAdapter);

NewsViewPagerFragment extends BaseViewPagerFragment

实现刚才说到的方法:

@Override
    protected void onSetupTabAdapter(ViewPageFragmentAdapter adapter) {
        String[] title = getResources().getStringArray(
                R.array.news_viewpage_arrays);
        adapter.addTab(title[0], "news", NewsFragment.class,
                getBundle(NewsList.CATALOG_ALL)); // 注一,用Bundle提供信息给Fragment
        adapter.addTab(title[1], "news_week", NewsFragment.class,
                getBundle(NewsList.CATALOG_WEEK));
        adapter.addTab(title[2], "latest_blog", BlogFragment.class,
                getBundle(BlogList.CATALOG_LATEST));
        adapter.addTab(title[3], "recommend_blog", BlogFragment.class,
                getBundle(BlogList.CATALOG_RECOMMEND));
    }

    private Bundle getBundle(int newType) {
        Bundle bundle = new Bundle();
        bundle.putInt(BaseListFragment.BUNDLE_KEY_CATALOG, newType);
        return bundle;
    }

    @Override
    protected void setScreenPageLimit() {
        mViewPager.setOffscreenPageLimit(3);
    }

可以看出,资讯和热点使用的同一个Fragment,博客和推荐也是同一个。
从mViewPager.setOffscreenPageLimit(3);来看,所有Tab页保留了下来。

比较有意思的是,它还实现了这样一个接口:

/** 
 * 当tabHost再次被点击时
 * @author FireAnt(http://my.oschina.net/LittleDY)
 * @version 创建时间:2014年11月17日 上午11:00:15 
 * 
 */
public interface OnTabReselectListener {
    
    public void onTabReselect();
}

具体来说是看该Fragment中嵌套的Fragment是否有实现此接口然后决定是否调用:

@Override
    public void onTabReselect() {
        try {
            int currentIndex = mViewPager.getCurrentItem();
            Fragment currentFragment = getChildFragmentManager().getFragments()
                    .get(currentIndex);
            if (currentFragment != null
                    && currentFragment instanceof OnTabReselectListener) {
                OnTabReselectListener listener = (OnTabReselectListener) currentFragment;
                listener.onTabReselect();
            }
        } catch (NullPointerException e) {
        }
    }

现在,我们来看综合页中的NewsFragment,同样是自顶向下。

BaseListFragment<T extends Entity> extends BaseFragment

这是一个泛型类,Bean包下有大量extends Entity的实体。
关于泛型可以参考这篇:http://www.jianshu.com/p/f258c907019d

使用了可以进行下拉刷新的布局,以下为代码片段:

<!-- google 官方下拉刷新 -->

    <android.support.v4.widget.SwipeRefreshLayout
        android:id="@+id/swiperefreshlayout"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:visibility="visible" >

        <ListView
            android:id="@+id/listview"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:divider="?attr/lineColor"
            android:listSelector="@color/transparent"
            android:dividerHeight="1px"
            android:scrollbars="none"
            android:scrollingCache="true" />
    </android.support.v4.widget.SwipeRefreshLayout>

提供了一个ParserTask extends AsyncTask,从代码中可以看到,其doInBackground中有如下调用:

new SaveCacheTask(getActivity(), data, getCacheKey()).execute();

这并不是一个好的实现,因为AsyncTask应该在主线程中创建,否则其内部的静态Handler将无法正常工作,具体可以参考《Android开发艺术探索》。
这里该应用的事件流中应该是ParserTask 本身有确保在主线程加载。

在onScrollStateChanged中提供滚动事件的特判,代码较长就不贴了。

提供上一篇中提到的已读列表功能:

/**
     * 保存已读的文章列表
     *
     * @param view
     * @param prefFileName
     * @param key
     */
    protected void saveToReadedList(final View view, final String prefFileName,
            final String key) {
        // 放入已读列表
        AppContext.putReadedPostList(prefFileName, key, "true");
        TextView tvTitle = (TextView) view.findViewById(R.id.tv_title);
        if (tvTitle != null) {
            tvTitle.setTextColor(AppContext.getInstance().getResources().getColor(ThemeSwitchUtils.getTitleReadedColor()));
        }
    }

onCreate()中读取前文注一提供的信息,实际使用当然会是在子类中了:

@Override
    public void onCreate(android.os.Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Bundle args = getArguments();
        if (args != null) {
            mCatalog = args.getInt(BUNDLE_KEY_CATALOG, 0); // 注二,子类中会使用此信息
        }
    }

onResume()中判断了是否要进行定时刷新操作:

@Override
    public void onResume() {
        super.onResume();
        if (onTimeRefresh()) {
            onRefresh();
        }
    }

NewsFragment extends BaseListFragment<News> implements OnTabReselectListener

明显使用了News这个Bean,取决于具体业务情况,略去不表。

Bean解析,可以看出数据使用的是xml格式,这一方法的实际调用位置在基类的ParserTask doInBackground中:

@Override
    protected NewsList parseList(InputStream is) throws Exception {
        NewsList list = null;
        try {
            list = XmlUtils.toBean(NewsList.class, is);
        } catch (NullPointerException e) {
            list = new NewsList();
        }
        return list;
    }

覆盖获取数据的方法,使用注二中提供的信息调用MobileAPI:

@Override
    protected void sendRequestData() {
        OSChinaApi.getNewsList(mCatalog, mCurrentPage, mHandler);
    }

之前提过的OnTabReselectListener有进行实现,可以看出是提供了一个刷新功能:

@Override
    public void onTabReselect() {
        onRefresh();
    }

而这个onRefresh()的实现是在基类BaseListFragment当中,也就是设置状态,ListView回到顶部,然后获取数据:

    // 下拉刷新数据
    @Override
    public void onRefresh() {
        if (mState == STATE_REFRESH) {
            return;
        }
        // 设置顶部正在刷新
        mListView.setSelection(0);
        setSwipeRefreshLoadingState();
        mCurrentPage = 0;
        mState = STATE_REFRESH;
        requestData(true);
    }

具体获取数据的时候,视情况看是读缓存还是发请求获取。
缓存的实现是CacheManager,非常纯粹的文件读写,略去不表。

根据业务情况,提供定时刷新的阈值给基类:

@Override
    protected long getAutoRefreshTime() {
        // 最新资讯两小时刷新一次
        if (mCatalog == NewsList.CATALOG_ALL) {

            return 2 * 60 * 60;
        }
        return super.getAutoRefreshTime();
    }

另外三个页面也是采用完全类似的方式,读者可以自行分析。
至此,综合页面分析完毕。下期再见。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,319评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,801评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,567评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,156评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,019评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,090评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,500评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,192评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,474评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,566评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,338评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,212评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,572评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,890评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,169评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,478评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,661评论 2 335

推荐阅读更多精彩内容