《简易新闻》源码分析

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 权限声明

1
2
3
4
5
<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 应用层

1
2
3
4
5
6
7
8
9
10
<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

1
public class MainActivity extends AppCompatActivity implements MainView

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

2.1 MainView接口

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

1
2
3
4
5
6
public interface MainView {
void switch2News();
void switch2Images();
void switch2Weather();
void switch2About();
}

2.2 onCreate()方法

2.2.1 布局文件

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

其中值得注意的是就是这个NavigationView:
导航抽屉界面

1
2
3
4
5
6
7
<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用来干什么,怎么用吧,没错,就是这样用的:

    1
    2
    3
    4
    5
    6
    7
    8
    <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。

1
2
3
4
5
6
7
8
<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 初始化视图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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设置了监听,实现了切换选项卡的效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 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 切换

1
2
3
4
5
@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 布局文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<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所选标签标志颜色,一张图看懂,此处改为蓝色
    tabIndicatorColor


  • layout_behavior: 滚动时自动消失Toolbar

3.2 视图初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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 初始化视图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@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 点击事件

1
2
3
4
5
6
7
8
9
10
11
12
13
@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内的视图创建。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@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其实在上文中有所提及,这里再进行一些补充。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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就实现了,不需要将每个参数都传入,二是为每个条目的点击事件设立了依赖注入,使其解耦。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@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 显示失败消息

1
2
3
4
5
6
7
8
9
@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 界面布局

布局页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
<?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 视图相关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@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. 新闻业务处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 加载新闻详情
* @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解析,思路容易理解,就不作分析了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@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