《简易新闻》源码分析

0. 前言

本文将对github上 liuling开发的基于Material Design和MVP的《简易新闻》源码进行简要分析,通过本文你将学到:

  • 阅读应用源码的步骤
  • RecyclerView
  • NavigationView
  • 下拉刷新和上拉加载
  • Material过渡动画
  • CollapsingToolbarLayout

1. 寻找入口

分析一个应用就是从MainActivity下手,那么如何找到MainActivity呢?当然还是通过Manifest文件,不过,在进入Manifest文件前,我们先来看看工程的一个结构。

1.1 工程总览

工程的目录结构如上图所示,有两个Module,一个是应用本身,还有一个是导入的swipeback库,用于滑动返回,如图:

1.2 Manifest文件

1.2.1 权限声明

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />

首先声明了权限,分别是网络相关和位置相关的权限。

1.2.2 应用层

<application
    ...
    android:supportsRtl="true"
    android:theme="@style/AppTheme">
    <activity
        android:name=".main.widget.MainActivity" ... >
    ...
    </activity>
    ...
</application>

这里Application标签中有一个属性 android:supportsRtl="true" 是什么意思呢?这是Android 4.2的一个新特性 layoutRtl,主要是方便开发者去支持阿拉伯语/波斯语等从右到左的阅读习惯。

接下来指明了两个主要的Activity:

- MainActivity

- NewsDetailActivity

2. MainActivity

public class MainActivity extends AppCompatActivity implements MainView

主Activity实现了MainView接口,所以我们来先看看该接口:

*

2.1 MainView接口

接口很简单,包含四个方法声明,分别是主界面的四个拨动页面。

public interface MainView {
    void switch2News();
    void switch2Images();
    void switch2Weather();
    void switch2About();
}

2.2 onCreate()方法

2.2.1 布局文件

首先来看主布局文件,布局可以说是简单易懂,清晰明了。一个DrawerLayout中夹了协调布局包裹的FrameLayout作为主界面和一个NavigationView。

其中值得注意的是就是这个NavigationView:

<android.support.design.widget.NavigationView
    android:id="@+id/navigation_view"
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    android:layout_gravity="start"
    app:headerLayout="@layout/navigation_header"
    app:menu="@menu/navigation_menu" />
  • app:headerLayout 属性: 头布局文件,及抽屉上方的个人头像。说起头像,就要用到CircleImageView,相信也会有读者像我一样曾经好奇过CircleImageView用来干什么,怎么用吧,没错,就是这样用的:
<de.hdodenhof.circleimageview.CircleImageView
    android:id="@+id/profile_image"
    android:layout_width="72dp"
    android:layout_height="72dp"
    android:layout_marginTop="20dp"
    android:src="@drawable/protrait"
    app:border_color="@color/primary_light"
    app:border_width="2dp" />
  • app:menu 属性: 使用菜单来填充选项,大家就不要以为只可以使用ListView自定义来实现菜单选择咯,但是笔者认为这里有个缺陷就是,抽屉会默认遮住状态栏和Toolbar。
<group android:checkableBehavior="single">
    <item
        android:id="@+id/navigation_item_news"
        android:icon="@drawable/ic_assessment_white_24dp"
        android:checked="true"
        android:title="@string/navigation_news" />
    ...
</group>

2.2.2 初始化视图

mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout, mToolbar, R.string.drawer_open,
        R.string.drawer_close);
mDrawerToggle.syncState();
mDrawerLayout.setDrawerListener(mDrawerToggle);
mNavigationView = (NavigationView) findViewById(R.id.navigation_view);
setupDrawerContent(mNavigationView);

private void setupDrawerContent(NavigationView navigationView) {
    navigationView.setNavigationItemSelectedListener(
            new NavigationView.OnNavigationItemSelectedListener() {
                @Override
                public boolean onNavigationItemSelected(MenuItem menuItem) {
                    mMainPresenter.switchNavigation(menuItem.getItemId());
                    menuItem.setChecked(true);
                    mDrawerLayout.closeDrawers();
                    return true;
                }
            });
}

首先实例化了ActionBar开关,同时调用syncState()同步状态,后面对mNavigationView设置了监听,实现了切换选项卡的效果。

// in class MainPresenterImpl
@Override
public void switchNavigation(int id) {
    switch (id) {
        case R.id.navigation_item_news:
            mMainView.switch2News();
            break;
        case R.id.navigation_item_images:
            mMainView.switch2Images();
            break;
        case R.id.navigation_item_weather:
            mMainView.switch2Weather();
            break;
        case R.id.navigation_item_about:
            mMainView.switch2About();
            break;
        default:
            mMainView.switch2News();
            break;
    }
}

2.2.3 切换

@Override
public void switch2News() {
    getSupportFragmentManager().beginTransaction().replace(R.id.frame_content, new NewsFragment()).commit();
    mToolbar.setTitle(R.string.navigation_news);
}

四个选项卡切换Fragment即可,那我们依次来看看这几个Fragment。

3. 新闻界面

3.1 布局文件

<android.support.design.widget.CoordinatorLayout
    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.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
        <android.support.design.widget.TabLayout
            android:id="@+id/tab_layout"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            app:tabIndicatorColor="@color/icons"/>
    </android.support.design.widget.AppBarLayout>

    <android.support.v4.view.ViewPager
        android:id="@+id/viewpager"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"/>

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

注意点

  • tabIndicatorColor: TabLayout所选标签标志颜色,一张图看懂,此处改为蓝色

  • layout_behavior: 滚动时自动消失Toolbar

3.2 视图初始化

    private void setupViewPager(ViewPager mViewPager) {
        //Fragment中嵌套使用Fragment一定要使用getChildFragmentManager(),否则会有问题
        MyPagerAdapter adapter = new MyPagerAdapter(getChildFragmentManager());
        adapter.addFragment(NewsListFragment.newInstance(NEWS_TYPE_TOP), getString(R.string.top));
        adapter.addFragment(NewsListFragment.newInstance(NEWS_TYPE_NBA), getString(R.string.nba));
        adapter.addFragment(NewsListFragment.newInstance(NEWS_TYPE_CARS), getString(R.string.cars));
        adapter.addFragment(NewsListFragment.newInstance(NEWS_TYPE_JOKES), getString(R.string.jokes));
        mViewPager.setAdapter(adapter);
    }

    public static class MyPagerAdapter extends FragmentPagerAdapter {
        private final List<Fragment> mFragments = new ArrayList<>();
        private final List<String> mFragmentTitles = new ArrayList<>();

        public MyPagerAdapter(FragmentManager fm) {
            super(fm);
        }

        public void addFragment(Fragment fragment, String title) {
            mFragments.add(fragment);
            mFragmentTitles.add(title);
        }

        @Override
        public Fragment getItem(int position) {
            return mFragments.get(position);
        }

        @Override
        public int getCount() {
            return mFragments.size();
        }

        @Override
        public CharSequence getPageTitle(int position) {
            return mFragmentTitles.get(position);
        }
    }

值得学习的是Adapter的写法,它将mFragments和mFragmentTitles两个List整合到了Adapter内部。还有要注意Fragment中嵌套使用Fragment一定要使用getChildFragmentManager(),接下来看其子项Fragment。

3.3 NewsListFragment

3.3.1 布局

布局很简单,一个SwipeRefreshLayout包裹RecyclerView。

3.3.2 初始化视图

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_newslist, null);

        mSwipeRefreshWidget = (SwipeRefreshLayout) view.findViewById(R.id.swipe_refresh_widget);
        mSwipeRefreshWidget.setColorSchemeResources(R.color.primary,
                R.color.primary_dark, R.color.primary_light,
                R.color.accent);
        mSwipeRefreshWidget.setOnRefreshListener(this);

        mRecyclerView = (RecyclerView)view.findViewById(R.id.recycle_view);
        mRecyclerView.setHasFixedSize(true);

        mLayoutManager = new LinearLayoutManager(getActivity());
        mRecyclerView.setLayoutManager(mLayoutManager);

        mRecyclerView.setItemAnimator(new DefaultItemAnimator());
        mAdapter = new NewsAdapter(getActivity().getApplicationContext());
        mAdapter.setOnItemClickListener(mOnItemClickListener);
        mRecyclerView.setAdapter(mAdapter);
        mRecyclerView.addOnScrollListener(mOnScrollListener);
        onRefresh();
        return view;
    }

值得提的是mSwipeRefreshWidget.setColorSchemeResources()方法可以设置刷新等待条的颜色;mRecyclerView.setItemAnimator()可以设置增加卡片动画。

3.3.3 点击事件

        @Override
        public void onItemClick(View view, int position) {
            NewsBean news = mAdapter.getItem(position);
            Intent intent = new Intent(getActivity(), NewsDetailActivity.class);
            intent.putExtra("news", news);

            View transitionView = view.findViewById(R.id.ivNews);
            ActivityOptionsCompat options =
                    ActivityOptionsCompat.makeSceneTransitionAnimation(getActivity(),
                            transitionView, getString(R.string.transition_news_img));

            ActivityCompat.startActivity(getActivity(), intent, options.toBundle());
        }

点击时,通过带有NewsBean参数的Intent启动新闻详情Activity,此外,在跳转页面的同时会有一个动画,通过以上代码可以实现动画。具体流程是先取得CardView中的ImageView,然后通过ActivityOptionsCompat makeSceneTransitionAnimation()方法取得过渡动画参数,并加在startActivity中。

3.3.4 上拉加载实现

上拉加载更多的实现主要有两个关键部分,一个是滚动事件的监听,另一个是Adapter内的视图创建。

private RecyclerView.OnScrollListener mOnScrollListener = new RecyclerView.OnScrollListener() {
    private int lastVisibleItem;
    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);
        lastVisibleItem = mLayoutManager.findLastVisibleItemPosition();
    }
    @Override
    public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
        super.onScrollStateChanged(recyclerView, newState);
        if (newState == RecyclerView.SCROLL_STATE_IDLE
                && lastVisibleItem + 1 == mAdapter.getItemCount()
                && mAdapter.isShowFooter()) {
            //加载更多
            LogUtils.d(TAG, "loading more data");
            mNewsPresenter.loadNews(mType, pageIndex + Urls.PAZE_SIZE);
        }
    }
};

滚动监听中判断三个条件:是否处于滚动暂停状态、当前页面的最后一个条目是否为所有信息中的最后一个条目、是否不处于正在加载新的条目状态。三个条件同时满足的情况下加载新条目。加载完新条目后又会调用mAdapter.isShowFooter(true)。

@Override
public int getItemViewType(int position) {
    // 最后一个item设置为footerView
    if(!mShowFooter) {
        return TYPE_ITEM;
    }
    if (position + 1 == getItemCount()) {
        return TYPE_FOOTER;
    } else {
        return TYPE_ITEM;
    }
}

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent,
                                                  int viewType) {
    if(viewType == TYPE_ITEM) {
        View v = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.item_news, parent, false);
        ItemViewHolder vh = new ItemViewHolder(v);
        return vh;
    } else {
        View view = LayoutInflater.from(parent.getContext()).inflate(
                R.layout.footer, null);
        view.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.WRAP_CONTENT));
        return new FooterViewHolder(view);
    }
}

onCreateViewHolder()方法是在每个条目创建时都会调用的方法,它用来填充视图,所以需要在这里进行选择需要创建的视图类型,这样便实现了上拉加载。

3.3.5 NewsAdapter

NewsAdapter其实在上文中有所提及,这里再进行一些补充。

    public class ItemViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {

        public TextView mTitle;
        public TextView mDesc;
        public ImageView mNewsImg;

        public ItemViewHolder(View v) {
            super(v);
            mTitle = (TextView) v.findViewById(R.id.tvTitle);
            mDesc = (TextView) v.findViewById(R.id.tvDesc);
            mNewsImg = (ImageView) v.findViewById(R.id.ivNews);
            v.setOnClickListener(this);
        }

        @Override
        public void onClick(View view) {
            if(mOnItemClickListener != null) {
                mOnItemClickListener.onItemClick(view, this.getPosition());
            }
        }
    }

首先是内部类ItemViewHolder,在这里有两个细节值得注意:一个是它的成员变量到初始化通过传入构造函数的View就实现了,不需要将每个参数都传入,二是为每个条目的点击事件设立了依赖注入,使其解耦。

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
    if(holder instanceof ItemViewHolder) {
        NewsBean news = mData.get(position);
        if(news == null) {
            return;
        }
        ((ItemViewHolder) holder).mTitle.setText(news.getTitle());
        ((ItemViewHolder) holder).mDesc.setText(news.getDigest());
        ImageLoaderUtils.display(mContext, ((ItemViewHolder) holder).mNewsImg, news.getImgsrc());
    }
}

public static void display(Context context, ImageView imageView, String url) {
    if(imageView == null) {
        throw new IllegalArgumentException("argument error");
    }
    Glide.with(context).load(url).placeholder(R.drawable.ic_image_loading)
            .error(R.drawable.ic_image_loadfail).crossFade().into(imageView);
}

在每个子条目的内容设置中,调用了Glide图片加载库进行图片的加载。关于Glide的使用读者可以参考这篇博客: Google推荐的图片加载库Glide介绍

3.3.6 显示失败消息

@Override
public void showLoadFailMsg() {
    if(pageIndex == 0) {
        mAdapter.isShowFooter(false);
        mAdapter.notifyDataSetChanged();
    }
    View view = getActivity() == null ? mRecyclerView.getRootView() : getActivity().findViewById(R.id.drawer_layout);
    Snackbar.make(view, getString(R.string.load_fail), Snackbar.LENGTH_SHORT).show();
}

调用Snackbar显示消息即可。

4. 新闻详情Activity

4.1 界面布局

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
    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:fitsSystemWindows="true">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="256dp"
        android:fitsSystemWindows="true"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">

        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/collapsing_toolbar"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:fitsSystemWindows="true"
            app:contentScrim="?attr/colorPrimary"
            app:expandedTitleMarginEnd="64dp"
            app:expandedTitleMarginStart="48dp"
            app:layout_scrollFlags="scroll|exitUntilCollapsed">

            <ImageView
                android:id="@+id/ivImage"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:fitsSystemWindows="true"
                android:scaleType="centerCrop"
                android:transitionName="@string/transition_news_img"
                app:layout_collapseMode="parallax"
                app:layout_collapseParallaxMultiplier="0.7"/>

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:layout_collapseMode="pin"
                app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>

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

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

    <android.support.v4.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

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

            <ProgressBar
                android:id="@+id/progress"
                style="?android:attr/progressBarStyle"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"/>

            <org.sufficientlysecure.htmltextview.HtmlTextView
                android:id="@+id/htNewsContent"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:padding="12dp"
                android:textAppearance="@android:style/TextAppearance.Medium"/>

        </LinearLayout>

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

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

这个页面布局代码全部都粘贴过来了,大家可想而知这个布局的重要性,让我们来细嚼一下这段布局代码。

4.1.1 CollapsingToolbarLayout

CollapsingToolbarLayout作用是提供了一个可以折叠的Toolbar,它继承至FrameLayout,给它设置layout_scrollFlags,它可以控制包含在CollapsingToolbarLayout中的控件(如:ImageView、Toolbar)在响应layout_behavior事件时作出相应的scrollFlags滚动事件(移除屏幕或固定在屏幕顶端)。

app相关属性介绍:

  1. 在CollapasingToolbarLayout 的属性:

    • app:contentScrim=”?attr/colorPrimary” — 设置此属性生,CollapsingToolbarLayout完成折叠动画后,Title部分会显示一个普通的颜色,代码中的颜色来自于style文件中的colorPrimary属性
    • app:expandedTitleMarginStart=”48dp” — 控制文本的边距
    • app:expandedTitleMarginEnd=”64dp” — 控制文本的边距
    • app:layout_scrollFlags —设置CollapsingToolbarLayout滚动折叠,关于这个属性需要详细解说,请看以下内容:
    • scroll -想要滚动就必须设置这个标记
    • exitUntilCollapsed -向上滚动收缩View,可以一直固定在ToolBar上面
    • enterAlwaysCollapsed -当你的View已经设置minHeight属性又使用此标志时,你的View只能以最小高度进入,只有当滚动视图到达顶部时才扩大到完整高度。
  2. 在ImageView控件中属性:
    • app:layout_collapseMode — 折叠模式 有俩个值

      • pin —设置这个模式时,当CollapsingToolbarLayout完全收缩后,ImageView显示的内容系统自己决定
      • parallax –设置这个模式时,当CollapsingToolbalLayout完全收缩后,ImageView显示的内容可以通过设置layout_collapseParallaxMultiplier来决定显示图片的哪部分内容
    • app:layout_collapseParallaxMultiplier=”0.7” 设置滚动视差,值为0~1。注意这个属性需要layout_collapseMode开启parallax模式后才会有作用,它决定CollapsingToolbarLayout完全折叠显示的内容
  3. CollapsingToolbarLayout配置完成之后,它下面的Layout必须设置layout_behavior属性来响应CollapsingToolbarLayout,如果没有配置layout_behavio,CollapsingToolbarLayout将没有折叠效果。

    注意:app:layout_behavior=”@string/appbar_scrolling_view_behavior”如果没有此属性那么CollapsingToolbarLayout将不会有折叠效果。

4.1.2 NestedScrollView

Android 在发布 Lollipop版本之后,为了更好的用户体验,Google为Android的滑动机制提供了NestedScrolling特性,这便是NestedScrollView,没什么多讲的,感兴趣的读者可以自行研究。

4.1.3 HtmlTextView

HtmlTextView是github上的一个开源框架,它是Android TextView控件的一个扩展,可以加载的HTML并将其转换成Spannable用于显示它。这是WebView组件的一个替代。

4.2 视图相关

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_news_detail);
    Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
    mProgressBar = (ProgressBar) findViewById(R.id.progress);
    mTVNewsContent = (HtmlTextView) findViewById(R.id.htNewsContent);
    setSupportActionBar(toolbar);
    getSupportActionBar().setDisplayHomeAsUpEnabled(true);
    toolbar.setNavigationOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            onBackPressed();
        }
    });
    mSwipeBackLayout = getSwipeBackLayout();
    mSwipeBackLayout.setEdgeSize(ToolsUtil.getWidthInPx(this));
    mSwipeBackLayout.setEdgeTrackingEnabled(SwipeBackLayout.EDGE_LEFT);
    mNews = (NewsBean) getIntent().getSerializableExtra("news");
    CollapsingToolbarLayout collapsingToolbar = (CollapsingToolbarLayout) findViewById(R.id.collapsing_toolbar);
    collapsingToolbar.setTitle(mNews.getTitle());
    ImageLoaderUtils.display(getApplicationContext(), (ImageView) findViewById(R.id.ivImage), mNews.getImgsrc());
    mNewsDetailPresenter = new NewsDetailPresenterImpl(getApplication(), this);
    mNewsDetailPresenter.loadNewsDetail(mNews.getDocid());
}

@Override
public void showNewsDetialContent(String newsDetailContent) {
    mTVNewsContent.setHtmlFromString(newsDetailContent, new HtmlTextView.LocalImageGetter());
}

首先值得一提的是SwipeBackLayout,这是一个滑动返回库,使用方法非常简单:

1. 继承SwipeBackActivity

2. mKeyTrackingMode = getString(R.string.key_tracking_mode);

3. mSwipeBackLayout = getSwipeBackLayout();

4. mSwipeBackLayout.setEdgeTrackingEnabled(edgeFlag);

5. saveTrackingMode(edgeFlag);

还有注意的是,在使用CollapsingToolbarLayout时,设置Toolbar标题要调用collapsingToolbar.setTitle(),而不是Toolbar的set方法,另外,笔者暂时还未发现如何为展开的Toolbar和折叠的Toolbar设置两个不同的标题,不过可以通过collapsingToolbar.setCollapsedTitleTextColor和collapsingToolbar.setExpandedTitleColor设置为不同颜色,并且底层自动处理颜色的过渡与渐变。

5. 新闻业务处理

/**
 * 加载新闻详情
 * @param docid
 * @param listener
 */
@Override
public void loadNewsDetail(final String docid, final OnLoadNewsDetailListener listener) {
    String url = getDetailUrl(docid);
    OkHttpUtils.ResultCallback<String> loadNewsCallback = new OkHttpUtils.ResultCallback<String>() {
        @Override
        public void onSuccess(String response) {
            NewsDetailBean newsDetailBean = NewsJsonUtils.readJsonNewsDetailBeans(response, docid);
            listener.onSuccess(newsDetailBean);
        }
        @Override
        public void onFailure(Exception e) {
            listener.onFailure("load news detail info failure.", e);
        }
    };
    OkHttpUtils.get(url, loadNewsCallback);
}

业务层主要包括网络请求和Json数据处理,这些主要通过框架来实现,由于此应用使用的框架在当今不太火热,所以就不作具体分析了。

6. 天气界面

天气界面主要看的就是布局和Json解析,思路容易理解,就不作分析了。

@Override
public void loadWeatherData(String cityName, final LoadWeatherListener listener) {
    try {
        String url = Urls.WEATHER + URLEncoder.encode(cityName, "utf-8");
        OkHttpUtils.ResultCallback<String> callback = new OkHttpUtils.ResultCallback<String>() {
            @Override
            public void onSuccess(String response) {
                List<WeatherBean> lists = WeatherJsonUtils.getWeatherInfo(response);
                listener.onSuccess(lists);
            }
            @Override
            public void onFailure(Exception e) {
                listener.onFailure("load weather data failure.", e);
            }
        };
        OkHttpUtils.get(url, callback);
    } catch (UnsupportedEncodingException e) {
        LogUtils.e(TAG, "url encode error.", e);
    }
}

总结

至此,《简易新闻》的代码分析就基本结束了,回顾全文,其实学到的不仅是Material的UI,还包括整个App的架构——MVP模式,该架构体现在每个模块,在代码方面,每个包以功能区分,在抽象方面每个模块又以MVP区分。最后,感谢读者的耐心阅读和App作者的无私奉献,然后祝大家学习进步!

参考

  1. android 4.2的新特性layoutRtl,让布局自动从右往左显示
  2. Google推荐的图片加载库Glide介绍
  3. [Android] 可以折叠的CollapsingToolbarLayout
  4. Android5.0+(CollapsingToolbarLayout)
  5. 支持加载Html内容的TextView:HtmlTextView for Android
时间: 2024-11-12 01:01:20

《简易新闻》源码分析的相关文章

仿网易新闻导航栏PagerSlidingTabStrip源码分析

转载请注明本文出自Cym的博客(http://blog.csdn.net/cym492224103),谢谢支持!   前言 最近工作比较忙,所以现在才更新博文,对不住大家了~!言归正传,我们来说说这个PagerSlidingTabStrip,它是配合ViewPager使用的导航栏,网易新闻就是用的这个导航,我们仔细观察这个导航栏不仅他是跟着ViewPager滑动而滑动,而且指示器还会随着标题的长度而动态的变化长度. · 下载地址: Github:https://github.com/astuet

ym——Android仿网易新闻导航栏PagerSlidingTabStrip源码分析

转载请注明本文出自Cym的博客(http://blog.csdn.net/cym492224103),谢谢支持! 前言 最近工作比较忙,所以现在才更新博文,对不住大家了~!言归正传,我们来说说这个PagerSlidingTabStrip,它是配合ViewPager使用的导航栏,网易新闻就是用的这个导航,我们仔细观察这个导航栏不仅他是跟着ViewPager滑动而滑动,而且指示器还会随着标题的长度而动态的变化长度,还可以改变多种样式哦~! · 下载地址: Github:https://github.

ASimpleCache(ACache)源码分析(android轻量级开源缓存框架)

转载请注明出处:http://blog.csdn.net/zhoubin1992/article/details/46379055 ASimpleCache框架源码链接 https://github.com/yangfuhai/ASimpleCache 杨神作品,大家最熟悉他的应该是afinal框架吧 官方介绍 ASimpleCache 是一个为android制定的 轻量级的 开源缓存框架.轻量到只有一个java文件(由十几个类精简而来). 1.它可以缓存什么东西? 普通的字符串.JsonObj

leveldb源码分析--Memtable

本节讲述内存中LevelDB的数据结构Memtable,Memtable义如其名即为内存中的KV Table,即LSM-Tree中的C0 Tree.我们知道在LSM-Tree中刚插入的的KV数据都是存储在内存中,当内存中存储的数据超过一定量以后再写到磁盘中.而对于leveldb来说这个过程演变为内存中的数据都是插入到MemTable中,当MemTable中的数据超过一定量(Options.write_buffer_size)以后MemTable就转化为Immutable Memtable等待du

Eoe客户端源码分析---SlidingMenu的使用

Eoe客户端源码分析及代码注释 使用滑动菜单SlidingMenu,单击滑动菜单的不同选项,可以通过ViewPager和PagerIndicator显示对应的数据内容. 0  BaseSlidingFragmentActivity.java 主要函数: (1)showMenu() /** * Opens the menu and shows the menu view.*/ public void showMenu() { showMenu(true); } (2)showContent() /

Android 上千实例源码分析以及开源分析

Android 上千实例源码分析以及开源分析(百度云分享) 要下载的直接翻到最后吧,项目实例有点多. 首先 介绍几本书籍(下载包中)吧. 01_Android系统概述 02_Android系统的开发综述 03_Android的Linux内核与驱动程序 04_Android的底层库和程序 05_Android的JAVA虚拟机和JAVA环境 06_Android的GUI系统 07_Android的Audio系统 08_Android的Video 输入输出系统 09_Android的多媒体系统 10_

Docker源码分析(八):Docker Container网络(下)

1.Docker Client配置容器网络模式 Docker目前支持4种网络模式,分别是bridge.host.container.none,Docker开发者可以根据自己的需求来确定最适合自己应用场景的网络模式. 从Docker Container网络创建流程图中可以看到,创建流程第一个涉及的Docker模块即为Docker Client.当然,这也十分好理解,毕竟Docker Container网络环境的创建需要由用户发起,用户根据自身对容器的需求,选择网络模式,并将其通过Docker Cl

Docker源码分析(七):Docker Container网络 (上)

1.前言(什么是Docker Container) 如今,Docker技术大行其道,大家在尝试以及玩转Docker的同时,肯定离不开一个概念,那就是“容器”或者“Docker Container”.那么我们首先从实现的角度来看看“容器”或者“Docker Container”到底为何物. 逐渐熟悉Docker之后,大家肯定会深深得感受到:应用程序在Docker Container内部的部署与运行非常便捷,只要有Dockerfile,应用一键式的部署运行绝对不是天方夜谭: Docker Conta

android缓存系列:ASimpleCache源码分析

接触Acache是因为阅读oschina的开源android端代码,发现oschina采用了该框架缓存新闻分页数据.后来知道这是个杨福海的开源项目,他还开源过afinal框架,项目的地址如下: https://github.com/yangfuhai/ASimpleCache 一.官方介绍 ASimpleCache 是一个为android制定的 轻量级的 开源缓存框架.轻量到只有一个java文件(由十几个类精简而来). 1.它可以缓存什么东西? 普通的字符串.JsonObject.JsonArr

dubbo源码分析6-telnet方式的管理实现

dubbo源码分析1-reference bean创建 dubbo源码分析2-reference bean发起服务方法调用 dubbo源码分析3-service bean的创建与发布 dubbo源码分析4-基于netty的dubbo协议的server dubbo源码分析5-dubbo的扩展点机制 dubbo提供了telnet的方式,直接用命令查看服务信息等.怎么实现的呢. 1. 编解码器 com.alibaba.dubbo.remoting.transport.netty.NettyCodecA