1. 概述
前邊我們通過兩節課來對Builder設計模式應該都有了一個了解和認識,如果對Builder設計模式還不是特別了解的,可以先去看下我前邊的兩篇文章
Builder設計模式構建自定義萬能的Dialog
從源碼角度分析AlertDialog,
那么這節課我們的內容還是對Builder設計模式的一個學習,我們學習下通過Builder設計模式如何去構建NavigationBar,來實現我們的導航欄。
2. 導航欄的幾種寫法
我們在項目中的導航欄一般都會有以下幾種寫法:
1>:直接在布局文件中寫一個 layout_title,然后通過include直接引入到每個布局文件中;
2>:用系統的 ToolBar;
3>:自定義View;
當然除過以上的幾種寫法,肯定也會有其他的寫法,在這里就不一一列舉了,那么我們下邊就針對于 使用 Builder設計模式構建NavigationBar方式來分析下
一般像頂部導航欄最好添加一些MetrialDesign的一些動畫效果會比較好看一些。
3. 代碼如下
頭部的基類AbsNavigationBar代碼如下:
/**
* Email: 2185134304@qq.com
* Created by JackChen 2018/4/5 18:43
* Version 1.0
* Params:
* Description: 頭部的基類
* 可能在整個項目中像這樣的布局用的地方不是很多,但是必須要有,
* 所以這里為了考慮頭部可能出現的所有情況,也把基本的布局考慮進去
*/
public abstract class AbsNavigationBar<P extends AbsNavigationBar.Builder.AbsNavigationParams> implements INavigationBar {
private P mParams;
private View mNavigationView;
public AbsNavigationBar(P params) {
this.mParams = params;
createAndBindView();
}
public P getParams() {
return mParams;
}
/**
* 設置文本
* @param viewId
* @param text
*/
protected void setText(int viewId, String text) {
TextView tv = findViewById(viewId);
if(!TextUtils.isEmpty(text)){
tv.setVisibility(View.VISIBLE);
tv.setText(text);
}
}
/**
* 設置點擊
* @param viewId
* @param listener
*/
protected void setOnClickListener(int viewId,View.OnClickListener listener){
findViewById(viewId).setOnClickListener(listener);
}
public <T extends View> T findViewById(int viewId){
return (T)mNavigationView.findViewById(viewId);
}
/**
* 綁定和創建View
*/
private void createAndBindView() {
// 1. 創建View
if(mParams.mParent == null){
// 獲取activity的根布局,View源碼
// 方法1:android.R.id.content就是 android.R.layout.screen_simple中的一個 FrameLayout的一個id
// ViewGroup activityRoot = (ViewGroup) ((Activity)(mParams.mContext))
// .findViewById(android.R.id.content);
// 方法2:.getWindow().getDecorView() 表示直接加載的就是 android.R.layout.screen_simple的根布局,而根布局就是一個 LinearLayout
// 所以不管 activity_main中的根布局是RelativeLayout、LinearLayout都可以
ViewGroup activityRoot = (ViewGroup) ((Activity)(mParams.mContext))
.getWindow().getDecorView() ;
mParams.mParent = (ViewGroup) activityRoot.getChildAt(0);
Log.e("TAG",mParams.mParent+"");
}
// 處理Activity的源碼,后面再去看
if(mParams.mParent == null){
return;
}
mNavigationView = LayoutInflater.from(mParams.mContext).
inflate(bindLayoutId(), mParams.mParent, false);// 插件換膚
// 2.添加
mParams.mParent.addView(mNavigationView, 0);
applyView();
}
// Builder 仿照系統寫的, 套路,活 AbsNavigationBar Builder 參數Params
public abstract static class Builder {
public Builder(Context context, ViewGroup parent) {
}
public abstract AbsNavigationBar builder();
public static class AbsNavigationParams {
public Context mContext;
public ViewGroup mParent;
public AbsNavigationParams(Context context, ViewGroup parent) {
this.mContext = context;
this.mParent = parent;
}
}
}
}
使用默認的頭部的 DefaultNavigationBar代碼如下:
public class DefaultNavigationBar<D extends
DefaultNavigationBar.Builder.DefaultNavigationParams> extends
AbsNavigationBar<DefaultNavigationBar.Builder.DefaultNavigationParams> {
public DefaultNavigationBar(DefaultNavigationBar.Builder.DefaultNavigationParams params) {
super(params);
}
@Override
public int bindLayoutId() {
return R.layout.title_bar;
}
@Override
public void applyView() {
// 綁定效果
setText(R.id.title, getParams().mTitle);
setText(R.id.right_text, getParams().mRightText);
setOnClickListener(R.id.right_text, getParams().mRightClickListener);
// 左邊 要寫一個默認的 finishActivity
setOnClickListener(R.id.back,getParams().mLeftClickListener);
}
public static class Builder extends AbsNavigationBar.Builder {
DefaultNavigationParams P;
public Builder(Context context, ViewGroup parent) {
super(context, parent);
P = new DefaultNavigationParams(context, parent);
}
public Builder(Context context) {
super(context, null);
P = new DefaultNavigationParams(context, null);
}
@Override
public DefaultNavigationBar builder() {
DefaultNavigationBar navigationBar = new DefaultNavigationBar(P);
return navigationBar;
}
// 1. 設置所有效果
public DefaultNavigationBar.Builder setTitle(String title) {
P.mTitle = title;
return this;
}
public DefaultNavigationBar.Builder setRightText(String rightText) {
P.mRightText = rightText;
return this;
}
/**
* 設置右邊的點擊事件
*/
public DefaultNavigationBar.Builder
setRightClickListener(View.OnClickListener rightListener) {
P.mRightClickListener = rightListener;
return this;
}
/**
* 設置左邊的點擊事件
*/
public DefaultNavigationBar.Builder
setLeftClickListener(View.OnClickListener rightListener) {
P.mLeftClickListener = rightListener;
return this;
}
/**
* 設置右邊的圖片
*/
public DefaultNavigationBar.Builder setRightIcon(int rightRes) {
return this;
}
public static class DefaultNavigationParams extends
AbsNavigationBar.Builder.AbsNavigationParams {
// 2.所有效果放置
public String mTitle;
public String mRightText;
// 后面還有一些通用的
public View.OnClickListener mRightClickListener;
public View.OnClickListener mLeftClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
// 關閉當前Activity
((Activity) mContext).finish();
}
};
public DefaultNavigationParams(Context context, ViewGroup parent) {
super(context, parent);
}
}
}
}
在MainActivity中的 initTitle()中直接 一句代碼引用即可:
public class MainActivity extends BaseSkinActivity {
@Override
protected void setContentView() {
setContentView(R.layout.activity_main);
}
@Override
protected void initData() {
}
@Override
protected void initView() {
}
@Override
protected void initTitle() {
DefaultNavigationBar navigationBar = new
DefaultNavigationBar.Builder(this)
.setTitle("投稿")
.builder();
}
}
注意:
在AbsNavigationBar中的這兩種方法要注意,這兩種方法代表的意思是一樣的
// 方法1:android.R.id.content就是 android.R.layout.screen_simple中的一個 FrameLayout的一個id
// ViewGroup activityRoot = (ViewGroup) ((Activity)(mParams.mContext))
// .findViewById(android.R.id.content);
// 方法2:.getWindow().getDecorView() 表示直接加載的就是 android.R.layout.screen_simple的根布局,而根布局就是一個 LinearLayout
// 所以不管 activity_main中的根布局是RelativeLayout、LinearLayout都可以
ViewGroup activityRoot = (ViewGroup) ((Activity)(mParams.mContext))
.getWindow().getDecorView() ;
mParams.mParent = (ViewGroup) activityRoot.getChildAt(0);
Log.e("TAG",mParams.mParent+"");
分析方法1:
方法1中的findViewById(android.R.id.content);是直接加載的是 系統的資源文件,即就是android.R.layout.screen_simple中的一個叫做 android.R.id.content的布局,它是一個FrameLayout,也就是說我們是把我們的 setContentView的布局其實是加載到 這個資源文件中的 android.R.id.content中的,它是一個 mContentParent,是一個 FrameLayout,也就是說我們是把 setContentView的布局是加載到 mContentParent中的,android.R.layout.screen_simple的布局文件如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:orientation="vertical">
<ViewStub android:id="@+id/action_mode_bar_stub"
android:inflatedId="@+id/action_mode_bar"
android:layout="@layout/action_mode_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="?attr/actionBarTheme" />
<FrameLayout
android:id="@android:id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:foregroundInsidePadding="false"
android:foregroundGravity="fill_horizontal|top"
android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>
分析方法2:
方法2其實就是 加載的是DecorView,而DecorView中加載的就是 系統的資源文件,就是 android.R.layout.screen_simple,其實就是直接加載該布局文件的根布局 LinearLayout,這樣的話,不管 我們自己的 setContentView()中根布局是 LinearLayout還是RelativeLayout,都可以直接把布局加載到第0個位置
上邊的兩種方法其實考的就是 setContentView()的加載流程,如果不是很懂的,可以去看下我前邊的文章,setContentView的加載流程分析
代碼已上傳至github:
https://github.com/shuai999/EssayJoke_day_08.git