上路傳送眼:
Android練手小項目(KTReader)基于mvp架構(一)
下路傳送眼:
Android練手小項目(KTReader)基于mvp架構(三)
GIthub地址: https://github.com/yiuhet/KTReader
上篇文章中我們完成了基類和啟動界面。
而這次我們要做的的就是能顯示知乎日報內(nèi)容的fragment奸焙。
這次我們使用到了開源框架Rxjava2+Okhttp3+retrofit2實現(xiàn)網(wǎng)絡請求,Glide加載圖片邻邮。
先附上效果圖:
準備工作
- 首先,添加依賴如下
compile 'com.github.bumptech.glide:glide:3.8.0'
compile 'com.squareup.okhttp3:okhttp:3.8.0'//貌似不用添加,retrofit2封裝了okhttp
compile 'com.squareup.okhttp3:logging-interceptor:3.8.0'
compile 'com.squareup.retrofit2:retrofit:2.3.0'
compile 'com.squareup.retrofit2:converter-gson:2.3.0'
compile 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'
compile 'com.google.code.gson:gson:2.8.0' //貌似不用添加威创,converter-gson中已經(jīng)封裝了gson庫
compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
compile 'io.reactivex.rxjava2:rxjava:2.1.0'
-
創(chuàng)建一個自定義的MyApplication,用來實現(xiàn)從任意位置獲取程序的context
app.MyApplication.class:
public class MyApplication extends Application {
private static Context sContext ;
private static String sCacheDir;
public static Context getContext() {
return sContext;
}
public static String getAppCacheDir() {
return sCacheDir;
}
@Override
public void onCreate() {
super.onCreate();
sContext = getApplicationContext();
if (getExternalCacheDir() != null && ExistSDCard()){
sCacheDir = getExternalCacheDir().toString();
} else {
sCacheDir = getCacheDir().toString();
}
}
private boolean ExistSDCard() {
return android.os.Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED);
}
}
創(chuàng)建好后別忘了配置AndroidManifest.xml:
在application內(nèi)添加
android:name=".app.MyApplication"
- 創(chuàng)建工具類和常量類
utils.NetWorkUtil.class: (判斷是否聯(lián)網(wǎng)的工具類)
public class NetWorkUtil {
private NetWorkUtil(){
};
public static boolean isNetWorkAvailable(Context context) {
ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
return networkInfo != null && networkInfo.isConnected();
}
public static boolean isWifiConnected(Context context) {
ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
return networkInfo != null && networkInfo.getType() == ConnectivityManager.TYPE_WIFI;
}
}
utils.CommonUtils.class:(目前只有彈toast的功能)
public class CommonUtils {
private static Toast mToast;
public static void ShowTips(Context context, String tips) {
if (mToast == null) {
mToast = Toast.makeText(context,tips,Toast.LENGTH_SHORT);
} else {
mToast.setText(tips);
}
mToast.show();
}
}
app.Constant.class :(常量類店茶,目前只有知乎的基本url)
public class Constant {
public static final String ZHIHU_BASE_URL = "http://news-at.zhihu.com/api/4/news/";
}
下面用到了retrofit2 + okhttp3 + rxjava3 的知識 附上參考資料
你真的會用Retrofit2嗎?Retrofit2完全教程
Android網(wǎng)絡編程(六)OkHttp3用法全解析
深入解析OkHttp3
Retrofit2+okhttp3攔截器處理在線和離線緩存
手把手教你使用 RxJava 2.0(一)
- 創(chuàng)建個RetrofitManager,處理網(wǎng)絡請求
utils.RetrofitManager.class:
public class RetrofitManager {
private static RetrofitManager retrofitManager;
private RetrofitManager() {
}
// 無論有無網(wǎng)絡都讀取緩存劫恒。(有時間限制) 把攔截器設置到addNetworkOnterceptor
private static Interceptor netInterceptor1 = new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response = chain.proceed(request);
int maxAge = 60; //60s為緩存的有效時間贩幻,60s內(nèi)獲取的是緩存數(shù)據(jù),超過60S我們就去網(wǎng)絡重新請求數(shù)據(jù)
return response
.newBuilder()
.removeHeader("Pragma")
.removeHeader("Cache-Control")
.header("Cache-Control", "public,max-age=" + maxAge)
.build();
}
};
//有網(wǎng)絡讀取網(wǎng)絡的數(shù)據(jù)两嘴,沒有網(wǎng)絡讀取緩存丛楚。
private static class netInterceptor2 implements Interceptor{
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
//沒有網(wǎng)絡時強制使用緩存數(shù)據(jù)
if (!NetWorkUtil.isNetWorkAvailable(MyApplication.getContext())) {
request = request.newBuilder()
//強制使用緩存數(shù)據(jù)
.cacheControl(CacheControl.FORCE_CACHE)
.build();
}
Response originalResponse = chain.proceed(request);
if (true) {
return originalResponse .newBuilder()
.removeHeader("Pragma")
.header("Cache-Control", "public,max-age=" + 0) //0為不進行緩存
.build();
} else {
int maxAge = 4 * 24 * 60 * 60; //緩存保存時間
return originalResponse .newBuilder()
.removeHeader("Pragma")
.header("Cache-Control", "public, only-if-cached, max-age=" + maxAge)
.build();
}
}
};
//緩存位置
private static File cacheFile = new File(MyApplication.getAppCacheDir(), "caheData_zhihu");
//設置緩存大小
private static int DEFAULT_DIR_CACHE = 10 * 1024 * 1024;
private static Cache cache = new Cache(cacheFile, DEFAULT_DIR_CACHE);
private static OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new netInterceptor2())
.addNetworkInterceptor(new netInterceptor2())
.cache(cache)
.build();
public static RetrofitManager getInstence() {
if (retrofitManager == null) {
synchronized (RetrofitManager.class) {
if (retrofitManager == null) {
retrofitManager = new RetrofitManager();
}
}
}
return retrofitManager;
}
private Retrofit retrofit;
public Retrofit getRetrofit(String url) {
if (retrofit == null) {
retrofit = new Retrofit.Builder()
.baseUrl(url) //必須以‘/’結尾
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())//使用RxJava2作為CallAdapter
.client(client)//如果沒有添加,那么retrofit2會自動給我們添加了一個。
.addConverterFactory(GsonConverterFactory.create())//Retrofit2可以幫我們自動解析返回數(shù)據(jù)憔辫,
.build();
}
return retrofit;
}
}
api.ZhihuApi:
public interface ZhihuApi {
@GET("latest")
Observable<ZhihuLatest> getZhihuLatest();
@GET("before/{date}")
Observable<ZhihuLatest> getBefore(@Path("date") String date);
}
Model層 :
模型實體類ZhihuLatest直接使用GsonFormat工具快速生成(model.entity.ZhihuLatest)
知乎日報Model接口
model.ZhihuLatestModel:
public interface ZhihuLatestModel {
void loadZhihuLatest(OnZhihuLatestListener listener);
void loadMore(OnZhihuLatestListener listener);
}
-
獲取知乎日報數(shù)據(jù)的Model實現(xiàn)
model.impq.ZhihuLatestModelImp1.class:
public class ZhihuLatestModelImp1 implements ZhihuLatestModel {
/*獲取知乎日報數(shù)據(jù)的Model實現(xiàn)*/
private ZhihuApi mZhihuApiService; //請求服務
private List<ZhihuLatest.StoriesEntity> mZhihuLatestList; //儲存entity的list趣些。
private String date; //網(wǎng)絡請求的url參數(shù),首次加載數(shù)據(jù)獲取贰您,調(diào)用getmore方法時坏平,date-1.
public ZhihuLatestModelImp1 () {
mZhihuLatestList = new ArrayList<>();
mZhihuApiService = RetrofitManager
.getInstence()
.getRetrofit("http://news-at.zhihu.com/api/4/news/")
.create(ZhihuApi.class); //創(chuàng)建請求服務
}
public List<ZhihuLatest.StoriesEntity> getmZhihuLatestList(){
return mZhihuLatestList;
}
@Override
public void loadZhihuLatest(final OnZhihuLatestListener listener) {
mZhihuLatestList.clear();
//數(shù)據(jù)層的操作拢操,網(wǎng)絡請求數(shù)據(jù)
if (mZhihuApiService != null) {
mZhihuApiService.getZhihuLatest()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<ZhihuLatest>() {
@Override
public void onSubscribe(@NonNull Disposable d) {
}
@Override
public void onNext(@NonNull ZhihuLatest zhihuLatest) {
date = zhihuLatest.date;
for (int i =0;i < zhihuLatest.stories.size(); i++) {
mZhihuLatestList.add(zhihuLatest.stories.get(i));
}
listener.onLoadZhihuLatestSuccess(); //加載成功時 回調(diào)接口方法。
}
@Override
public void onError(@NonNull Throwable e) {
listener.onLoadDataError(e.toString());//加載失敗時 回調(diào)接口方法功茴。
}
@Override
public void onComplete() {
}
});
}
}
@Override
public void loadMore(final OnZhihuLatestListener listener) {
// date = String.valueOf(Integer.parseInt(date) - 1); 2333,之前犯傻直接減1就當求前一天了
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
Calendar calendar = new GregorianCalendar();;//獲取日歷實例
try {
calendar.setTime(sdf.parse(date));
calendar.add(Calendar.HOUR_OF_DAY, -1); //設置為前一天
date = sdf.format(calendar.getTime());//獲得前一天
} catch (ParseException e) {
e.printStackTrace();
}
//數(shù)據(jù)層的操作庐冯,網(wǎng)絡請求數(shù)據(jù)
if (mZhihuApiService != null) {
mZhihuApiService.getBefore(date)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<ZhihuLatest>() {
@Override
public void onSubscribe(@NonNull Disposable d) {
}
@Override
public void onNext(@NonNull ZhihuLatest zhihuLatest) {
for (int i =0;i < zhihuLatest.stories.size(); i++) {
mZhihuLatestList.add(zhihuLatest.stories.get(i));
}
listener.onLoadMoreSuccess();//加載成功時 回調(diào)接口方法孽亲。
}
@Override
public void onError(@NonNull Throwable e) {
listener.onLoadDataError(e.toString());//加載失敗時 回調(diào)接口方法坎穿。
}
@Override
public void onComplete() {
}
});
}
}
}
View層
首先我們先確定需要實現(xiàn)的功能
- 從知乎日報上拉取數(shù)據(jù) ( 知乎日報 API 分析)
- 當屏幕拉到底部時加載更多數(shù)據(jù)
- 創(chuàng)建回調(diào)接口
view.ZhihuView:
public interface ZhihuView {
void onStartGetData();
void onGetZhihuLatestSuccess();
void onGetMoreSuccess();
void onGetDataFailed(String error);
}
- 在創(chuàng)建ZhiHuFragment之前,我們要先創(chuàng)建一個組件和adapter
widget.ZhihuItem:
public class ZhihuItem extends RelativeLayout {
private Context mContext;
@BindView(R.id.zhihu_iv)
ImageView mZhihuIv;
@BindView(R.id.zhihu_title)
TextView mZhihuTitle;
public ZhihuItem(Context context) {
this(context, null);
}
public ZhihuItem(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
init();
}
private void init() {
LayoutInflater.from(getContext()).inflate(R.layout.view_zhihu_item, this);
ButterKnife.bind(this, this);
}
public void bindView(ZhihuLatest.StoriesEntity zhihuLatest) {
mZhihuTitle.setText(zhihuLatest.title);
String url = zhihuLatest.images.get(0).toString();
//Glide 獲取圖片
Glide.with(mContext)
.load(url)
.placeholder(R.drawable.loading) //占位圖片
.error(R.drawable.error) //錯誤圖片
.into(mZhihuIv);
}
}
組件的布局文件:
view_zhihu_item.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/zhihu_iv"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_margin="5dp"
android:layout_alignParentStart="true"/>
<TextView
android:layout_centerVertical="true"
android:id="@+id/zhihu_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/zhihu_iv"
android:layout_marginLeft="20dp"
android:textColor="@android:color/black"
android:textSize="18dp" />
</RelativeLayout>
創(chuàng)建的ZhihuAdapter自己寫了點擊監(jiān)聽器接口返劲,會在fragment里添加監(jiān)聽事件玲昧。
adapter.ZhihuAdapter:
public class ZhihuAdapter extends RecyclerView.Adapter<ZhihuAdapter.ZhihuViewHolder> {
private Context mContext;
List<ZhihuLatest.StoriesEntity> mZhihuLatestList;
private OnItemClickListener mItemClickListener;
public ZhihuAdapter(Context context, List<ZhihuLatest.StoriesEntity> zhihuLatestList) {
mContext =context;
mZhihuLatestList = zhihuLatestList;
}
@Override
public ZhihuViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
ZhihuItem zhihuItem = new ZhihuItem(mContext);
return new ZhihuViewHolder(zhihuItem);
}
@Override
public void onBindViewHolder(ZhihuViewHolder holder, int position) {
final ZhihuLatest.StoriesEntity zhihuLatest = mZhihuLatestList.get(position);
holder.zhihuItem.bindView(zhihuLatest);
holder.zhihuItem.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mItemClickListener != null) {
mItemClickListener.onItemClick(zhihuLatest.id);
}
}
});
}
@Override
public int getItemCount() {
return mZhihuLatestList.size();
}
public class ZhihuViewHolder extends RecyclerView.ViewHolder {
public ZhihuItem zhihuItem;
public ZhihuViewHolder(ZhihuItem itemView) {
super(itemView);
zhihuItem = itemView;
}
}
public interface OnItemClickListener {
void onItemClick(int id);
}
public void setOnItemClickListener(OnItemClickListener listener) {
mItemClickListener = listener;
}
}
-
創(chuàng)建ZhiHuFragment
ui.fragment.ZhiHuFragment:
public class ZhiHuFragment extends BaseFragment<ZhihuView, ZhihuPresenterImp1> implements ZhihuView {
@BindView(R.id.recycle_zhihu)
RecyclerView mRecycleZhihu;
Unbinder unbinder;
@BindView(R.id.prograss)
ProgressBar mPrograss;
private ZhihuAdapter mZhihuAdapter;
@Override
public void onStartGetZhihuLatest() {
mPrograss.setVisibility(View.VISIBLE);
}
@Override
public void onGetZhihuLatestSuccess() {
mPrograss.setVisibility(View.GONE);
mZhihuAdapter.notifyDataSetChanged();
}
@Override
public void onGetZhihuLatestFailed(String error) {
mPrograss.setVisibility(View.GONE);
toast(error);
}
@Override
public void onStartGetMore() {
mPrograss.setVisibility(View.VISIBLE);
}
@Override
public void onGetMoreSuccess() {
mPrograss.setVisibility(View.GONE);
mZhihuAdapter.notifyDataSetChanged();
}
@Override
public void onGetMoreFailed(String error) {
mPrograss.setVisibility(View.GONE);
toast(error);
}
@Override
protected int getLayoutRes() {
return R.layout.fragment_zhihu;
}
@Override
protected ZhihuPresenterImp1 createPresenter() {
return new ZhihuPresenterImp1(this);
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// TODO: inflate a fragment view
View rootView = super.onCreateView(inflater, container, savedInstanceState);
unbinder = ButterKnife.bind(this, rootView);
init();
mPresenter.getLatest();
return rootView;
}
private void init() {
mRecycleZhihu.setLayoutManager(new LinearLayoutManager(getContext()));
mRecycleZhihu.setHasFixedSize(true);
mRecycleZhihu.addItemDecoration(new DividerItemDecoration(getActivity(), DividerItemDecoration.VERTICAL));
mRecycleZhihu.setItemAnimator(new DefaultItemAnimator());
mRecycleZhihu.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (isSlideToBottom(recyclerView)) {
mPresenter.getMore();
}
}
});
mZhihuAdapter = new ZhihuAdapter(getContext(), mPresenter.getmZhihuLatestList());
mZhihuAdapter.setOnItemClickListener(mOnItemClickListener);
mRecycleZhihu.setAdapter(mZhihuAdapter);
}
public static boolean isSlideToBottom(RecyclerView recyclerView) {
if (recyclerView.computeVerticalScrollExtent() + recyclerView.computeVerticalScrollOffset()
>= recyclerView.computeVerticalScrollRange())
return true;
return false;
}
private ZhihuAdapter.OnItemClickListener mOnItemClickListener = new ZhihuAdapter.OnItemClickListener() {
@Override
public void onItemClick(int id) {
toast(Constant.ZHIHU_BASE_URL + String.valueOf(id));
}
};
@Override
public void onDestroyView() {
super.onDestroyView();
unbinder.unbind();
}
}
Presenter層
在ZhihuPresenterImp1類里實現(xiàn)數(shù)據(jù)和視圖的綁定
-
先寫一個回調(diào)接口:
(在Presenter層實現(xiàn),給Model層回調(diào)篮绿,更改View層的狀態(tài)孵延,確保Model層不直接操作View層)
presenter.OnZhihuLatestListener:
public interface OnZhihuLatestListener {
/**
* 成功時回調(diào)
*/
void onLoadZhihuLatestSuccess();
void onLoadMoreSuccess();
/**
* 失敗時回調(diào)
*/
void onLoadDataError(String error);
}
-
再寫一個presenter接口:
presenter.ZhihuPresenter :
public interface ZhihuPresenter {
void getLatest();
void getMore();
}
-
最后寫Prestener實現(xiàn)類:
presenter.imp1.ZhihuPresenterImp1.class:
public class ZhihuPresenterImp1 extends BasePresenter<ZhihuView> implements ZhihuPresenter,OnZhihuLatestListener{
/*Presenter作為中間層,持有View和Model的引用*/
private ZhihuView mZhihuView;
private ZhihuLatestModelImp1 zhihuLatestModelImp1;
public ZhihuPresenterImp1(ZhihuView zhihuView) {
mZhihuView = zhihuView;
zhihuLatestModelImp1 = new ZhihuLatestModelImp1();
}
public List<ZhihuLatest.StoriesEntity> getmZhihuLatestList() {
return zhihuLatestModelImp1.getmZhihuLatestList();
}
@Override
public void getLatest() {
mZhihuView.onStartGetData();
zhihuLatestModelImp1.loadZhihuLatest(this);
}
@Override
public void getMore() {
mZhihuView.onStartGetData();
zhihuLatestModelImp1.loadMore(this);
}
@Override
public void onLoadZhihuLatestSuccess() {
mZhihuView.onGetZhihuLatestSuccess();
}
@Override
public void onLoadMoreSuccess() {
mZhihuView.onGetMoreSuccess();
}
@Override
public void onLoadDataError(String error) {
mZhihuView.onGetDataFailed(error);
}
}
最后亲配,創(chuàng)建一個帶有側滑菜單的MainActivity
- 暫且只在其內(nèi)部添加一個ZhiHuFragment尘应。
- 側滑菜單具體功能之后會實現(xiàn)。
- 雙擊返回鍵退出
ui.activity.MainActivity.class:
public class MainActivity extends BaseActivity
implements NavigationView.OnNavigationItemSelectedListener {
@BindView(R.id.toolbar)
Toolbar mToolbar;
@BindView(R.id.fragment_main)
FrameLayout fragmentMain;
@BindView(R.id.nav_view)
NavigationView mNavView;
@BindView(R.id.drawer_layout)
DrawerLayout mDrawerLayout;
private long exitTime = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ButterKnife.bind(this);
initView();
getSupportFragmentManager().beginTransaction().add(R.id.fragment_main, new ZhiHuFragment()).commit();
}
private void initView() {
setSupportActionBar(mToolbar);
ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
this, mDrawerLayout, mToolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
mDrawerLayout.addDrawerListener(toggle);
toggle.syncState();
mNavView.setNavigationItemSelectedListener(this);
}
@Override
protected int getLayoutRes() {
return R.layout.activity_main;
}
@Override
public void onBackPressed() {
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
if (drawer.isDrawerOpen(GravityCompat.START)) {
drawer.closeDrawer(GravityCompat.START);
} else {
if ((System.currentTimeMillis() - exitTime) > 2000) {
CommonUtils.ShowTips(MainActivity.this, "再點一次吼虎,退出");
exitTime = System.currentTimeMillis();
} else {
super.onBackPressed();
}
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
@SuppressWarnings("StatementWithEmptyBody")
@Override
public boolean onNavigationItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.nav_camera) {
// Handle the camera action
}
mDrawerLayout.closeDrawer(GravityCompat.START);
return true;
}
修改布局文件
activity_main.xml:
<android.support.v4.widget.DrawerLayout 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:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:openDrawer="start">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include
layout="@layout/app_bar_main"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/fragment_main">
</FrameLayout>
</LinearLayout>
<android.support.design.widget.NavigationView
android:id="@+id/nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true"
app:headerLayout="@layout/nav_header_main"
app:menu="@menu/activity_main_drawer" />
</android.support.v4.widget.DrawerLayout>
app_bar_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.AppBarLayout
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:theme="@style/AppTheme.AppBarOverlay"
tools:context="com.example.yiuhet.ktreader.ui.activity.MainActivity">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay" />
</android.support.design.widget.AppBarLayout>