Collection聚合了專案搭建的一些基本模組,節約開發者時間,協助專案的快速搭建,RecyclerView+Adapter+Retrofit+RxJava+MVP+DataManager+基本Base,能夠滿足一個專案的基本實現。
github地址:https://github.com/usernameyangyan/Collection-Android
掘金地址:https://juejin.im/post/5ab9987451882555635e5401
更新說明
v1.2.7
1.增加自定義控制元件TabLayout。
v1.2.6
1.RxJava的依賴更新。 2.修正RecyclerView頭部佈局不能鋪滿問題。 3.PopupWindow的使用。 4.DisplayUtils工具類對狀態列的修改。
v1.2.5
1.修正Retrofit DEFAULT_POST請求方式指向錯誤。
2.Retrofit 資料解析相容沒有公用been類,可以指定公用been類和不指定公用been類、或者混合使用。
3.Realm增加資料遷移(資料庫欄位增加或移除)。
4.增加幾種通用的Dialog彈窗,提供方法自定義。
5.提供幾種比較常用的Utils工具類。
v1.2.4
1.增加DataManager用來統一管理資料請求,包括Retrofit的請求、SharePreference以及Realm的資料請求。
2.Retrofit的請求的整合。 3.PullToRefreshRecyclerView的空佈局bug修改。
文章目錄
1.框架的引入
2.PullToRefreshRecyclerView的使用
- 框架預設下拉重新整理、上拉載入更多樣式
- 自定義下拉重新整理、上拉載入更多樣式
- 上拉載入更多結合SwipeRefreshLayout使用
- RecyclerView新增頭部、空佈局
- 上拉載入更多實現NoMoreData、自動重新整理
3.BaseRecyclerViewAdapter的使用
- BaseRecyclerViewAdapter比原始Adapter程式碼量減少
- 新增Item的點選事件
- 新增Item的長按事件
- 多佈局的使用
- 新增拖拽、滑動刪除
4.MVP+RxJava+Retrofit的封裝使用
- 框架中的Retrofit+RxJava封裝的瞭解
- 使用框架在專案需要做的操作
- MVP+RxJava+Retrofit+OkHttp的快取機制
- MVP+RxJava+Retrofit+自定義磁碟快取機制
5.DataManager的使用
- DataManager的Retrofit請求
- DataManager的SharePreference的使用
- DataManager的Realm的使用
6.Base的使用
- Base封裝了MVP和專案的基類
- UI狀態控制StateView的使用
- 三步實現Permission(許可權)設定
- 提供幾種比較常用的Dialog彈框
- 提供幾種比較常用的PopupWindow彈框
- 使用DisplayUtils修改狀態列
- 提供幾種比較常用的Utils工具類
7.CustomView的使用
- CommonTabLayout的使用
- OutSideFrameTabLayout的使用
一、框架整體模組
二、框架的引入
implementation 'com.youngman:collectionlibrary:1.2.7' compile 'com.youngman:collectionlibrary:1.2.7'
Error:Could not find com.android.support:appcompat-v7:27.x.x. 因為library的Support Repository是27.x.x,可能跟專案有所衝突,如果sdk已經裝了27還是會出現同樣的錯誤。 解決辦法:在專案根build.gradle中加入 maven { url "https://maven.google.com" }
三 、PullToRefreshRecyclerView的使用
1.框架預設下拉重新整理、上拉載入更多樣式
(1)佈局檔案
<com.youngmanster.collectionlibrary.refreshrecyclerview.pulltorefresh.PullToRefreshRecyclerView
android:id="@+id/recycler_rv"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
複製程式碼
(2)程式碼設定
mRecyclerView.setPullRefreshEnabled(true);
mRecyclerView.setLoadMoreEnabled(true);
複製程式碼
2、自定義下拉重新整理、上拉載入更多樣式
(1)程式碼設定
mRecyclerView.setPullRefreshEnabled(true);
mRecyclerView.setLoadMoreEnabled(true);
mRecyclerView.setRefreshView(new DefinitionAnimationRefreshHeaderView(getActivity()));
mRecyclerView.setLoadMoreView(new DefinitionAnimationLoadMoreView(getActivity()));
複製程式碼
(2)自定義重新整理和載入更多樣式
在進行自定義View之前先來了解重新整理和載入更多的幾種狀態:
① BasePullToRefreshView重新整理:
/***
* 下拉重新整理分為4個狀態
*/
//下拉的狀態(還沒到下拉到固定的高度時)
public static final int STATE_PULL_DOWN=0;//
//下拉到固定高度提示釋放重新整理的狀態
public static final int STATE_RELEASE_REFRESH=1;
//重新整理狀態
public static final int STATE_REFRESHING=2;
//重新整理完成
public static final int STATE_DONE=3;
複製程式碼
②BaseLoadMoreView載入更多:
/***
* 載入更多分為3個狀態
*/
//正在載入
public final static int STATE_LOADING = 0;
//載入完成
public final static int STATE_COMPLETE = 1;
//沒有資料
public final static int STATE_NODATA= 2;
複製程式碼
自定義重新整理的步驟:
①自定義View繼承BasePullToRefreshView,重寫initView()、setRefreshTimeVisible(boolean show)、destroy()方法:
在initView()做自定義佈局、相關動畫的初始化,最後在initView()方法的最後面新增以下程式碼即可。
//mContainer =LayoutInflater.from(context).inflate(R.layout.layout_default_arrow_refresh, null);
//把重新整理頭部的高度初始化為0
LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
lp.setMargins(0, 0, 0, 0);
this.setLayoutParams(lp);
this.setPadding(0, 0, 0, 0);
addView(mContainer, new LayoutParams(LayoutParams.MATCH_PARENT, 0));//把重新整理佈局新增進去
setGravity(Gravity.BOTTOM);
//測量高度
measure(ViewGroup.LayoutParams.WRAP_CONTENT,ViewGroup.LayoutParams.WRAP_CONTENT);
mMeasuredHeight = getMeasuredHeight();
複製程式碼
setRefreshTimeVisible(boolean show)是用來設定是否顯示重新整理時間控制元件,在預設重新整理樣式中通過mRecyclerView.setRefreshTimeVisible(false)即可隱藏重新整理時間,如果在自定義的佈局中沒有這項這個方法就可以忽略。
destroy()是用來關掉改頁面時把重新整理View的一些動畫等釋放,防止記憶體洩漏。
②實現BasePullToRefreshView.OnStateChangeListener監聽(重點,主要是進行狀態切換後的相關操作邏輯)
在建構函式中
onStateChangeListener=this;
複製程式碼
onStateChange的模板樣式
@Override
public void onStateChange(int state) {
//下拉時狀態相同不做繼續保持原有的狀態
if (state == mState) return ;
//根據狀態進行動畫顯示
switch (state){
case STATE_PULL_DOWN://跟隨手指下拉的狀態
//clearAnim();
//startAnim();
break;
case STATE_RELEASE_REFRESH://下拉釋放
break;
case STATE_REFRESHING://正在進行重新整理
//clearAnim();
//startAnim();
scrollTo(mMeasuredHeight);//這段程式碼需要新增
break;
case STATE_DONE://重新整理完成
break;
}
mState = state;//狀態的更新
}
複製程式碼
自定義載入更多的步驟(包括沒有更多資料顯示的操作):
①自定義View繼承BaseLoadMoreView,重寫initView()、setState()、destroy()方法:
在initView()做自定義佈局、相關動畫的初始化,最後在initView()方法的最後面新增以下程式碼即可。
//mContainer = LayoutInflater.from(context).inflate(R.layout.layout_definition_animation_loading_more, null);
addView(mContainer);
setGravity(Gravity.CENTER);
複製程式碼
destroy()是用來關掉改頁面時把重新整理View的一些動畫等釋放,防止記憶體洩漏。
在setState()進行狀態切換後的相關操作邏輯,模板樣式:
@Override
public void setState(int state) {
switch (state){
case STATE_LOADING://正在載入
//loadMore_Ll.setVisibility(VISIBLE);
//noDataTv.setVisibility(INVISIBLE);
//animationDrawable= (AnimationDrawable) loadingIv.getDrawable();
//animationDrawable.start();
this.setVisibility(VISIBLE);//這段程式碼需要新增
break;
case STATE_COMPLETE:
//if(animationDrawable!=null){
// animationDrawable.stop();
//}
this.setVisibility(GONE);//這段程式碼需要新增
break;
case STATE_NODATA:
//loadMore_Ll.setVisibility(INVISIBLE);
//noDataTv.setVisibility(VISIBLE);
//animationDrawable= (AnimationDrawable) loadingIv.getDrawable();
//animationDrawable.start();
this.setVisibility(VISIBLE);//這段程式碼需要新增
break;
}
mState = state;//狀態的更新
}
複製程式碼
②注意:在自定義載入更多樣式時,如果需要有沒有更多載入更多資料提示同樣需要在佈局中寫好,然後在onSatae中根據狀態對載入和沒有跟多顯示提示進行顯示隱藏操作。
3、上拉載入更多配合SwipeRefreshLayout使用
(1)佈局檔案
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/swipeRefreshLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.youngmanster.collectionlibrary.refreshrecyclerview.pulltorefresh.PullToRefreshRecyclerView
android:id="@+id/recycler_rv"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</android.support.v4.widget.SwipeRefreshLayout>
複製程式碼
(2)程式碼設定
mRecyclerView.setLoadMoreEnabled(true);
mRecyclerView.setLoadMoreView(new DefinitionAnimationLoadMoreView(getActivity()));
swl_Refresh.setColorSchemeResources(R.color.colorAccent);
swl_Refresh.setOnRefreshListener(this);
複製程式碼
(3)注意的問題
由於PullToRefreshRecyclerView的下拉重新整理和下拉載入更多完成時會自動重新整理Adapter,而SwipeRefreshLayout重新整理完成時需要手動進行notifyDataSetChanged重新整理介面卡。
4、RecyclerView新增頭部、空佈局
(1)程式碼設定
View emptyView = LayoutInflater.from(getActivity()).inflate(R.layout.layout_empty,null);
mRecyclerView.setEmptyView(emptyView);
複製程式碼
5、上拉載入更多實現NoMoreData、自動重新整理
(1)上拉載入更多資料的佈局設定在上面的自定義LoadingMoreView中有介紹,如果要顯示沒有更多資料提示只需要在LoadMore返回資料之後設定:
mRecyclerView.setNoMoreDate(true);
複製程式碼
(2)自動重新整理需要列表已經填充了資料之後再做自動重新整理操作才會生效:
mRecyclerView.setAutoRefresh();
複製程式碼
6、PullToRefreshRecyclerView的其他使用以及注意問題
(1)提供的使用方法
mRecyclerView.isLoading() //是否正在載入更多
mRecyclerView.loadMoreComplete() //載入更多完成
mRecyclerView.isRefreshing() //是否正在重新整理
mRecyclerView.refreshComplete(); //重新整理資料完成
複製程式碼
(2)重新整理、載入更多介面回撥,PullToRefreshRecyclerView.OnRefreshAndLoadMoreListener提供一下兩個方法:
onRecyclerViewRefresh()
onRecyclerViewLoadMore()
複製程式碼
(3)使用例項部分程式碼:
//下拉重新整理、上拉載入更多設定
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getActivity());
linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
refreshRv.setLayoutManager(linearLayoutManager);
refreshRv.setRefreshView(new DefinitionAnimationRefreshHeaderView(getActivity()));
refreshRv.setLoadMoreView(new DefinitionAnimationLoadMoreView(getActivity()));
refreshRv.setPullRefreshEnabled(true);
refreshRv.setLoadMoreEnabled(true);
refreshRv.setRefreshAndLoadMoreListener(this);
============================下面是下拉重新整理上拉載入更多的一些操作=========================================
//重新整理頁面
@Override
public void refreshUI(List<WeChatNews> newsList) {
//先做資料拼接
if(newsList!=null){
if (pageSize == 1) {
mDatas.clear();
mDatas.addAll(newsList);
} else {
mDatas.addAll(newsList);
}
}
if (weChatFeaturedAdapter == null) {
//配合StateView使用,StateView具體使用下面有介紹
if(mDatas.size()==0){
stateView.showViewByState(StateView.STATE_EMPTY);
}else{
stateView.showViewByState(StateView.STATE_NO_DATA);
}
weChatFeaturedAdapter = new WeChatFeaturedAdapter(getActivity(), mDatas, refreshRv);
refreshRv.setAdapter(weChatFeaturedAdapter);
} else {
//判斷該操作是下拉重新整理還是上拉載入更多
if (refreshRv.isLoading()) {
refreshRv.loadMoreComplete();
//如果沒有更多資料就顯示沒有更多資料提示
if (newsList==null||newsList.size() == 0) {
refreshRv.setNoMoreDate(true);
}
} else if (refreshRv.isRefreshing()) {
refreshRv.refreshComplete();
}
}
}
複製程式碼
(4)其他注意問題
①在設定RecyclerView是要設LayoutManager
②如果使用PullToRefreshRecyclerView在Activty/Fragment中的onDestroy()呼叫mRecyclerView.destroy()防止記憶體洩漏。
@Override
public void onDestroy() {
super.onDestroy();
if(mRecyclerView!=null){
mRecyclerView.destroy();
}
}
複製程式碼
③設定refreshRv.setLoadMoreEnabled(true),當填充的資料的列表size為0的同時還通過RecyclerView設定分割線底部就會出現一個空白的item,這個item就是載入更多顯示的Item。
解決辦法:不通過RecyclerView設定分割線,直接在佈局自定義分割線。
四、BaseRecyclerViewAdapter的使用
1.BaseRecyclerViewAdapter的比原始Adapter的程式碼量減小
在BaseRecyclerViewAdapter中的BaseViewHolder進行佈局轉化,同時定義了一些比較基本的View操作,使用簡單。
(1)使用程式碼:
public class PullToRecyclerViewAdapter extends BaseRecyclerViewAdapter<String> {
public PullToRecyclerViewAdapter(Context mContext, List<String> mDatas, PullToRefreshRecyclerView pullToRefreshRecyclerView) {
super(mContext, R.layout.item_pull_refresh, mDatas, pullToRefreshRecyclerView);
}
@Override
protected void convert(BaseViewHolder baseViewHolder, String s) {
baseViewHolder.setText(R.id.title,s);
}
}
複製程式碼
①使用者需要在繼承BaseRecyclerViewAdapter時傳入一個資料實體型別,具體的操作在convert()方法中操作。
②BaseViewHolder提供了一些常用View的基本操作,通過baseViewHolder.getView()可得到佈局中的控制元件。
(2)BaseRecyclerViewAdapter提供了兩個建構函式
public BaseRecyclerViewAdapter(Context mContext, int mLayoutResId, List<T> mDatas, PullToRefreshRecyclerView pullToRefreshRecyclerView) {
this.mContext = mContext;
this.mLayoutResId = mLayoutResId;
this.mDatas = mDatas;
this.mRecyclerView=pullToRefreshRecyclerView;
}
public BaseRecyclerViewAdapter(Context mContext, int mLayoutResId, List<T> mDatas) {
this.mContext = mContext;
this.mLayoutResId = mLayoutResId;
this.mDatas = mDatas;
}
複製程式碼
主要是對PullToRefreshRecyclerView和RecyclerView的適配,使用時介面卡根據需要使用對應的建構函式。
2.新增Item的點選和長按事件
(1) Item點選事件實現
itemClickAdapter.setOnItemClickListener(new BaseRecyclerViewAdapter.OnItemClickListener() {
@Override
public void onItemClick(View view, int position) {
showToast(mDatas.get(position).getTitle());
}
});
複製程式碼
(2)Item長按事件實現
itemClickAdapter.setOnItemLongClickListener(new BaseRecyclerViewAdapter.onItemLongClickListener() {
@Override
public boolean onItemLongClick(View view, int position) {
showToast("進行長按操作");
return true;
}
});
複製程式碼
(3)也可以實現BaseRecyclerViewAdapter.OnItemClickListener和BaseRecyclerViewAdapter.onItemLongClickListener
//事件監聽
itemClickAdapter.setOnItemClickListener(this);
itemClickAdapter.setOnItemLongClickListener(this);
//點選實現
@Override
public void onItemClick(View view, int position) {
showToast(mDatas.get(position).getTitle());
}
@Override
public boolean onItemLongClick(View view, int position) {
showToast("進行長按操作");
return true;
}
複製程式碼
3.多佈局的使用
BaseRecyclerViewAdapter的多佈局實現需要注意的四步:
①自定義Adapter需要繼承BaseRecyclerViewMultiItemAdapter。
② 資料實體類需要繼承BaseMultiItemEntity,在getItemViewType()返回佈局型別。
③ 在自定義Adapter中的建構函式中通過addItemType()傳入不同型別對應的佈局。
④在自定義Adapter中的convert進行型別判斷,做相對應的操作。
public class MultipleAdapter extends BaseRecyclerViewMultiItemAdapter<MultiItem> {
private int mHeight;
public MultipleAdapter(Context mContext, List<MultiItem> mDatas) {
super(mContext, mDatas);
mHeight = DisplayUtil.dip2px(mContext, 100);
addItemType(MultiItem.TYPE_TEXT, R.layout.item_main);
addItemType(MultiItem.TYPE_IMG, R.layout.item_img);
addItemType(MultiItem.TYPE_TEXT_IMG, R.layout.item_click);
}
@Override
protected void convert(BaseViewHolder baseViewHolder, MultiItem multiItem) {
switch (baseViewHolder.getItemViewType()) {
case MultiItem.TYPE_TEXT:
baseViewHolder.getView(R.id.card_view).getLayoutParams().height = mHeight;
baseViewHolder.setText(R.id.title, multiItem.getTitle());
break;
case MultiItem.TYPE_IMG:
baseViewHolder.setImageResource(R.id.ivImg, multiItem.getRes());
break;
case MultiItem.TYPE_TEXT_IMG:
baseViewHolder.setImageResource(R.id.ivImg, multiItem.getRes());
baseViewHolder.setText(R.id.titleTv, multiItem.getTitle());
break;
}
}
複製程式碼
4.新增拖拽、滑動刪除
侷限:只針對RecyclerView,對本框架封裝的PullToRefreshRecyclerView會出現混亂。
①BaseRecyclerViewAdapter和BaseRecyclerViewMultiItemAdapter都已經封裝支援拖拽、滑動,介面卡只需要根據需求繼承其中一個即可。
②框架提供了一個BaseRecycleItemTouchHelper,對於普通的左右滑動刪除、拖拽已經實現,如果想自定義可以繼承BaseRecycleItemTouchHelper類,再重寫相對應的方法進行實現。
④在Activity/Fragment中需要實現以下程式碼:
ItemTouchHelper.Callback callback=new BaseRecycleItemTouchHelper(dragAndDeleteAdapter);
ItemTouchHelper itemTouchHelper=new ItemTouchHelper(callback);
itemTouchHelper.attachToRecyclerView(mRecyclerView);
複製程式碼
⑤BaseRecyclerViewAdapter.OnDragAndDeleteListener進行操作動作完成之後的回撥。
@Override
public void onDragAndDeleteFinished() {
mRecyclerView.postDelayed(new Runnable() {
@Override
public void run() {
dragAndDeleteAdapter.notifyDataSetChanged();
showToast("操作完成");
}
},300);
}
複製程式碼
注意:需要延時再進行邏輯操作,不然會出現資料混亂。
四、MVP+RxJava+Retrofit的封裝使用
由於Retrofit已經封裝在DataManager中,在DataManager中有詳細的介紹,這裡只是提供一個例子讓大家瞭解如何使用MVP+RxJava+Retrofit。
1.在使用Retrofit請求網路之前需要進行配置,在框架中提供了了Config配置類
框架中的Config總覽如下:
public class Config {
/**必傳引數**/
//是否為BuildConfig.DEBUG,日誌輸出需要
public static boolean DEBUG;
//設定Context
public static Context CONTEXT;
/**Retrofit**/
//網路請求的域名
public static String URL_DOMAIN;
//網路快取地址
public static String URL_CACHE;
//設定OkHttp的快取機制的最大快取時間,預設為一天
public static long MAX_CACHE_SECONDS= 60 * 60 * 24;
//快取最大的記憶體,預設為10M
public static long MAX_MEMORY_SIZE=10 * 1024 * 1024;
//設定網路請求json通用解析類
public static Class MClASS;
/**SharePreference**/
public static String USER_CONFIG;
/**Realm**/
public static RealmMigration realmMigration;
public static int realmVersion=0;
public static String realmName="myRealm.realm";
}
複製程式碼
在專案中需要根據專案需要進行配置,在Application中設定
private void config(){
Config.DEBUG= BuildConfig.DEBUG;//這個如果是測試時,日誌輸出,網路請求相關資訊輸出
Config.URL_CACHE=AppConfig.URL_CACHE;//OkHttp快取地址
Config.CONTEXT=this;//這個是必傳
Config.MClASS= Result.class;//如果專案的json資料格式統一可以設定一個統一的been類
Config.URL_DOMAIN="http://api.tianapi.com/";//網路請求域名
}
複製程式碼
根據專案需要定義一個通用的資料實體類,這是本例通用實體類,這個類需要設定到Applicatin中
public class Result<T> implements Serializable {
private int code;
private String msg;
private T newslist;
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public T getNewslist() {
return newslist;
}
public void setNewslist(T newslist) {
this.newslist = newslist;
}
}
複製程式碼
溫馨提醒:由於每個專案返回來的json資料格式有所不同,如果Result中代表的欄位例如newslist沒有內容返回來的時候這個欄位需要後臺控制不返回,如果不做處理會報解析錯誤。
3.MVP+RxJava+Retrofit+OkHttp的快取機制
上面的快取配置完成之後通過以下程式碼即可:
public class WeChatWorldNewsPresenter extends WeChatWorldNewsContract.Presenter {
@Override
public void requestWorldNews(int page, int num) {
RequestBuilder<Result<List<WeChatNews>>> resultRequestBuilder = new RequestBuilder<>(new RxObservableListener<Result<List<WeChatNews>>>(mView) {
@Override
public void onNext(Result<List<WeChatNews>> result) {
mView.refreshUI(result.getNewslist());
}
});
resultRequestBuilder
.setUrl(ApiUrl.URL_WETCHAT_WORLD_NEWS)
.setTransformClass(WeChatNews.class)
.setRequestParam(ApiClient.getRequiredBaseParam())
.setHttpTypeAndReqType(RequestBuilder.HttpType.DEFAULT_GET, RequestBuilder.ReqType.DEFAULT_CACHE_LIST)
.setParam("page",page)
.setParam("num",num);
rxManager.addObserver(DataManager.getInstance(DataManager.DataType.RETROFIT).httpRequest(resultRequestBuilder));
}
}
複製程式碼
4.MVP+RxJava+Retrofit+自定義磁碟快取機制
public class WeChatChinaNewsDefinitionPresenter extends WeChatChinaNewsContract.Presenter {
@Override
public void requestChinaNews(int page, int num) {
String filePath = AppConfig.STORAGE_DIR + "wechat/china";
String fileName = "limttime.t";
RequestBuilder resultRequestBuilder = new RequestBuilder<>(new RxObservableListener<Result<List<WeChatNews>>>(mView) {
@Override
public void onNext(Result<List<WeChatNews>> result) {
mView.refreshUI(result.getNewslist());
}
}).setFilePathAndFileName(filePath, fileName)
.setTransformClass(WeChatNews.class)
.setUrl(ApiUrl.URL_WETCHAT_CHINA_NEWS)
.setRequestParam(ApiClient.getRequiredBaseParam())
.setHttpTypeAndReqType(RequestBuilder.HttpType.DEFAULT_GET,RequestBuilder.ReqType.DISK_CACHE_LIST_LIMIT_TIME)
.setParam("page", page)
.setParam("num", num);
rxManager.addObserver(DataManager.getInstance(DataManager.DataType.RETROFIT).httpRequest(resultRequestBuilder));
}
}
複製程式碼
注意:
①RxObservableListener有三個回撥方法
void onNext(T result);
void onComplete();
void onError(NetWorkCodeException.ResponseThrowable e);
複製程式碼
只會重寫onNext方法,其它兩個方法可以自行選擇重寫。
②RxObservableListener提供兩個建構函式
protected RxObservableListener(BaseView view){
this.mView = view;
}
protected RxObservableListener(BaseView view, String errorMsg){
this.mView = view;
this.mErrorMsg = errorMsg;
}
複製程式碼
這兩個建構函式主要主要是為了統一處理onError的,如果要自定義錯誤提醒,則可以選擇第二個建構函式。
③通過DataManager的網路請求方式會返回來一個DisposableObserver,需要把它通過rxManager.addObserver()新增進CompositeDisposable才能正常執行。
五、DataManager的使用(DataManager封裝了三種資料請求方式,包括Retroift、SharePreference和Realm)
1.DataManager的瞭解
提供了三種方式
public enum DataType {
RETROFIT, REALM, SHAREPREFERENCE
}
複製程式碼
通過DataManager.getInstance(DataManager.DataType.XXX)可獲得對應的請求方式。
1.DataManager的Retrofit請求
(1)配置
需要在專案的Application初始化Retrofit的一些引數
//基本配置
Config.DEBUG= BuildConfig.DEBUG;
Config.CONTEXT=this;
//Retrofit配置
Config.URL_CACHE=AppConfig.URL_CACHE;
Config.MClASS= Result.class;//如果專案的json資料格式統一可以設定一個統一的been類
Config.URL_DOMAIN="http://api.tianapi.com/";
複製程式碼
(2)使用統一解析類、不使用統一解析類、混合使用
① 如果專案如果專案的json資料格式統一可以設定一個統一的been類,例如上面的例子的Result類,同時要在Config類設定(下面例子都是有統一解析類):
RequestBuilder<Result<List<WeChatNews>>> resultRequestBuilder = new RequestBuilder<>(new RxObservableListener<Result<List<WeChatNews>>>(mView) {
@Override
public void onNext(Result<List<WeChatNews>> result) {
mView.refreshUI(result.getNewslist());
}
});
resultRequestBuilder
.setUrl(ApiUrl.URL_WETCHAT_FEATURED)
.setTransformClass(WeChatNews.class)
.setRequestParam(ApiClient.getRequiredBaseParam())
.setParam("page",page)
.setParam("num",num);
rxManager.addObserver(DataManager.getInstance(DataManager.DataType.RETROFIT).httpRequest(resultRequestBuilder));
複製程式碼
②如果專案沒有統一的解析been類,那麼Config類就不用設定了,在Retrofit請求的時候直接指定一個解析類就可以了:
RequestBuilder<WeChatNewsResult> resultRequestBuilder = new RequestBuilder<>(new RxObservableListener<WeChatNewsResult>(mView) {
@Override
public void onNext(WeChatNewsResult result) {
mView.refreshUI(result.getNewslist());
}
});
resultRequestBuilder
.setUrl(ApiUrl.URL_WETCHAT_FEATURED)
.setTransformClass(WeChatNewsResult.class)
.setRequestParam(ApiClient.getRequiredBaseParam())
.setParam("page",page)
.setParam("num",num);
rxManager.addObserver(DataManager.getInstance(DataManager.DataType.RETROFIT).httpRequest(resultRequestBuilder));
複製程式碼
③如果專案想兩種方式共存,那麼在請求的時候需要通過setUserCommonClass(false)設定才能不使用統一解析類進行解析:
RequestBuilder<WeChatNewsResult> resultRequestBuilder = new RequestBuilder<>(new RxObservableListener<WeChatNewsResult>(mView) {
@Override
public void onNext(WeChatNewsResult result) {
mView.refreshUI(result.getNewslist());
}
});
resultRequestBuilder
.setUrl(ApiUrl.URL_WETCHAT_FEATURED)
.setTransformClass(WeChatNewsResult.class)
.setUserCommonClass(false)
.setRequestParam(ApiClient.getRequiredBaseParam())
.setParam("page",page)
.setParam("num",num);
rxManager.addObserver(DataManager.getInstance(DataManager.DataType.RETROFIT).httpRequest(resultRequestBuilder));
複製程式碼
注意:DISK_CACHE_LIST_LIMIT_TIME和DISK_CACHE_MODEL_LIMIT_TIME這兩種限時使用快取的請求方式不統一一種解析方式會出現頁面沒有資料顯示,因為在限定的時間內如果突然轉用另外一個解析實體類去解析會解析失敗,只能等過限定時間或者清除本地快取去解決這一問題。
(2)RequestBuilder的設定(網路請求的配置)
①資料處理的方式
public enum ReqType {
//沒有快取
NO_CACHE_MODEL,
No_CACHE_LIST,
//預設Retrofit快取
DEFAULT_CACHE_MODEL,
DEFAULT_CACHE_LIST,
//自定義磁碟快取,返回List
DISK_CACHE_LIST_LIMIT_TIME,
//自定義磁碟快取,返回Model
DISK_CACHE_MODEL_LIMIT_TIME,
//自定義磁碟快取,沒有網路返回磁碟快取,返回List
DISK_CACHE_NO_NETWORK_LIST,
//自定義磁碟快取,沒有網路返回磁碟快取,返回Model
DISK_CACHE_NO_NETWORK_MODEL,
//儲存網路資料到本地磁碟,可以設定網路請求是否返回資料
DISK_CACHE_NETWORK_SAVE_RETURN_MODEL,
DISK_CACHE_NETWORK_SAVE_RETURN_LIST,
}
複製程式碼
②網路請求方式
public enum HttpType {
//GET請求
DEFAULT_GET,
//POST請求
DEFAULT_POST,
//如果請求URL出現中文亂碼,可選擇這個
FIELDMAP_POST,
//上傳一張圖片
ONE_MULTIPART_POST
}
複製程式碼
③RequestBuilder的填充
RequestBuilder<Result<List<WeChatNews>>> resultRequestBuilder = new RequestBuilder<>(new RxObservableListener<Result<List<WeChatNews>>>(mView) {
@Override
public void onNext(Result<List<WeChatNews>> result) {
mView.refreshUI(result.getNewslist());
}
});
resultRequestBuilder
.setUrl(ApiUrl.URL_WETCHAT_WORLD_NEWS)
.setTransformClass(WeChatNews.class)
.setHttpTypeAndReqType(RequestBuilder.HttpType.DEFAULT_GET, RequestBuilder.ReqType.DEFAULT_CACHE_LIST)
.setRequestParam(ApiClient.getRequiredBaseParam())
.setParam("page",page)
.setParam("num",num);
複製程式碼
④DataManager提供Retrofit請求的方法
<T> DisposableObserver<ResponseBody> httpRequest(RequestBuilder<T> requestBuilder);
複製程式碼
(3)Retrofit的擴充套件
如果存在DataManager提供的方法滿足不了的請求可以通過RetrofitManager提供的getNoCacheApiService()和getApiService()獲得不快取和快取的Retrofit,然後通過RxSubscriber進行回撥。
Observable<WeChatAccessToken> observable = RetrofitManager.getNoCacheApiService(ApiService.class)
.getWeChatStr(ApiUrl.URL_WECHAT_HOST + ApiUrl.ACCESS_TOKEN, reqParams);
DisposableObserver<WeChatAccessToken> observer = observable
.compose(RxSchedulers.<WeChatAccessToken>io_main())
.subscribeWith(new RxSubscriber<WeChatAccessToken>() {
@Override
public void _onNext(WeChatAccessToken weChatAccessToken) {
getUserInfo(weChatAccessToken);
}
@Override
public void _onError(NetWorkCodeException.ResponseThrowable responseThrowable) {
showToast(R.string.wx_LoginResultEmpty);
hideLoadingDialog();
finish();
}
@Override
public void _onComplete() {
}
});
rxManager.addObserver(observer);
複製程式碼
定義一個ApiService類
public interface ApiService {
/**
* 微信精選
* @param url
* @param map
* @return
*/
@GET
Observable<Result<List<WeChatNews>>> getWeChatFeaturedNews(@Url String url, @QueryMap Map<String,Object> map);
}
複製程式碼
(4)注意的問題
①請求的域名已經在Application設定好了,setUrl不需要填完整的url
②要區分清楚介面返回的資料時List還是Model,從而選擇對應的ReqType
③setRequestParam可以設定引數集合,setParam可以單個設定
④使用DISK_CACHE_LIST_LIMIT_TIME/DISK_CACHE_MODEL_LIMIT_TIME這兩個顯示限時快取時需要通過setFilePathAndFileName()設定儲存路徑setLimtHours()設定快取時間(單位為:小時)
⑤如果要上傳單張圖片需要用到HttpType.ONE_MULTIPART_POST的請求方式,同時通過RequestBuilder設定MultipartBody.Part
2.DataManager的SharePreference的使用
(1)配置
需要在專案的Application初始化SharePreference的一些引數
//SharePreference配置
Config.USER_CONFIG="Collection_User";
複製程式碼
(2)使用方法
DataManager.getInstance(DataManager.DataType.SHAREPREFERENCE).saveByKeyWithSP("user","這
String user=DataManager.getInstance(DataManager.DataType.SHAREPREFERENCE).queryByKeyWithSP("user",String.class);是一條測試的內容");
複製程式碼
(3)DataManager提供SharePreference請求的方法
//自定義儲存的配置檔名、key
void saveByNameAndKeyWithSP(String name, String key, Object object);
//使用在Application配置的儲存檔名
void saveByKeyWithSP(String key,Object object);
//查詢儲存在自定義的配置檔案的內容
<T> T queryByNameAndKeyWithSP(String name, String key, Class<T> clazz);
//查詢儲存在Application設定的檔案的內容
<T> T queryByKeyWithSP(String key, Class<T> clazz);
複製程式碼
3.DataManager的Realm的使用
(1)配置
①需要在專案的Application初始化Realm的一些引數
//Realm的配置
Config.realmVersion=0;
Config.realmName="realm.realm";
Config.realmMigration=customMigration;//資料庫資料遷移(been類欄位增加移除)
複製程式碼
②在Project 的build.gradle中的dependencies加入
classpath "io.realm:realm-gradle-plugin:5.0.0"
複製程式碼
③在專案 的build.gradle中的頂部加入
apply plugin: 'realm-android'
複製程式碼
(2)使用方法
DataManager.getInstance(DataManager.DataType.REALM).saveOrUpdateWithPKByRealm(user);
user= (User) DataManager.getInstance(DataManager.DataType.REALM).queryFirstByRealm(User.class);
複製程式碼
(3)DataManager提供Realm請求的方法
/**
* 儲存操作
*/
void saveOrUpdateWithPKByRealm(final RealmObject bean);
void saveOrUpdateWithPKByRealm(final List<? extends RealmObject> beans);
void saveWithoutPKByRealm(final RealmObject bean);
void saveWithoutPKByRealm(final List<? extends RealmObject> beans);
/**
* 查詢操作
*/
RealmObject queryFirstByRealm(Class<? extends RealmObject> clazz);
RealmObject queryAllWithFieldByRealm(Class<? extends RealmObject> clazz, String fieldName, String value);
List<? extends RealmObject> queryAllByRealm(Class<? extends RealmObject> clazz);
List<? extends RealmObject> queryAllWithSortByRealm(Class<? extends RealmObject> clazz, String fieldName,Boolean isAscendOrDescend);
/**
* 修改操作
*/
void updateParamWithPKByRealm(Class<? extends RealmObject> clazz, String primaryKeyName, Object primaryKeyValue, String fieldName,Object newValue);
/**
* 刪除操作
*/
void deleteFirstByRealm(Class<? extends RealmObject> clazz);
void deleteAllByRealm(Class<? extends RealmObject> clazz);
複製程式碼
(4)Realm資料遷移(been類欄位增加移除)
隨著app版本的迭代,資料庫的欄位可能會增加或者移除這時候就需要用到Realm提供的RealmMigration進行設定。
public class CustomMigration implements RealmMigration {
@Override
public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {
RealmSchema schema = realm.getSchema();
if (oldVersion == 0 && newVersion == 1) {
RealmObjectSchema personSchema = schema.get("User");
personSchema
.addField("age", int.class);
oldVersion++;
}else if(oldVersion == 1&&newVersion==2){
RealmObjectSchema personSchema = schema.get("User");
personSchema
.addField("address", String.class);
oldVersion++;
}
}
}
複製程式碼
步驟:
- 自定義RealmMigration,在migrate方法中進行欄位的增加或者移除。
- 在Application中升Realm的版本號Config.realmVersion往上增加。
- 在Application設定RealmMigration,Config.realmMigration=customMigration。
(5)注意的問題
- 自定義Realm的儲存檔案文成的時候需要以.realm為字尾。
六、 Base的使用
1.Base封裝了MVP和專案的基類
(1)MVP
- BaseModel
- BaseView
- BasePresenter
(2)在專案中的網路請求+MVP的完整實現
①定義一個contract類,內部分別繼承上面的MVP base類,在這裡定義操作。
public interface WeChatChinaNewsContract {
interface View extends BaseView {
void refreshUI(List<WeChatNews> weChatNews);
}
abstract class Presenter extends BasePresenter<View> {
public abstract void requestChinaNews(int page,int num);
}
}
複製程式碼
②Presenter的具體執行類。
public class WeChatChinaNewsPresenter extends WeChatChinaNewsContract.Presenter {
@Override
public void requestChinaNews(int page, int num) {
RequestBuilder resultRequestBuilder = new RequestBuilder<>(new RxObservableListener<Result<List<WeChatNews>>>(mView) {
@Override
public void onNext(Result<List<WeChatNews>> result) {
mView.refreshUI(result.getNewslist());
}
}).setUrl(ApiUrl.URL_WETCHAT_CHINA_NEWS)
.setTransformClass(WeChatNews.class)
.setRequestParam(ApiClient.getRequiredBaseParam())
.setParam("page", page)
.setParam("num", num);
rxManager.addObserver(DataManager.getInstance(DataManager.DataType.RETROFIT).httpRequest(resultRequestBuilder));
}
}
複製程式碼
③UI
public class FragmentChinaNews extends BaseFragment<WeChatChinaNewsModel,WeChatChinaNewsPresenter> implements WeChatChinaNewsContract.View{
@Override
public void init() {
}
@Override
public void requestData() {
((WeChatChinaNewsPresenter)mPresenter).requestChinaNews(pageSize,PAGE_SIZE);
}
@Override
public void refreshUI(List<WeChatNews> newsList) {
}
@Override
public void onError(String errorMsg) {
}
}
複製程式碼
2.UI Base
(1)IBaseActivity
-
IBaseActivity:主要提供了一個頁面的基本方法、處理了MVP之間的關聯、使用者可以直接繼承該類使用、也可以繼承該類實現擴充套件。
-
IBaseActivity<T extends BaseModel, E extends BasePresenter>已經進行MVP之間的傳遞和關聯。
-
處理好頁面銷燬之後Observables 和 Subscribers的解綁。
-
getLayoutId()設定佈局、init()資料初始化、requestData()請求資料,執行順序已經在IBaseActivity做好處理。
-
可以繼承IBaseActivity進行擴充套件。
public abstract class BaseActivity<T extends BaseModel,E extends BasePresenter> extends IBaseActivity{ private Unbinder unbinder;
@Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); unbinder= ButterKnife.bind(this); } 複製程式碼
}
缺陷:如果對IBaseActivity進行擴充套件,在具體呼叫時需要型別才能呼叫相關方法。
@Override
public void requestData() {
((WeChatFeaturedPresenter) mPresenter).requestFeaturedNews(pageSize, PAGE_SIZE);
}
複製程式碼
(2)IBaseFragment
-
IBaseFragment:主要提供一個頁面的基本方法,處理了MVP之間的關聯,該類已經加入了懶人載入的控制方式、使用者可以直接繼承該類使用、也可以繼承該類實現擴充套件。
-
IBaseFragment<T extends BaseModel,E extends BasePresenter>已經進行MVP之間的傳遞和關聯。
-
處理好頁面銷燬之後Observables 和 Subscribers的解綁。
-
加入了懶人載入方式,只有頁面顯示才會呼叫requestData()請求資料,並只會呼叫一次。
-
getLayoutId()設定佈局、init()資料初始化、requestData()請求資料,執行順序已經在IBaseFragment做好處理。
-
可以繼承IBaseFragment進行擴充套件。
public abstract class BaseFragment<T extends BaseModel,E extends BasePresenter> extends IBaseFragment { private Unbinder unbinder; @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { super.onCreateView(inflater, container, savedInstanceState); unbinder= ButterKnife.bind(this,mainView); return mainView; } } 複製程式碼
缺陷:如果對IBaseActivity進行擴充套件,在具體呼叫時需要型別才能呼叫相關方法。
@Override
public void requestData() {
((WeChatChinaNewsPresenter)mPresenter).requestChinaNews(pageSize,PAGE_SIZE);
}
複製程式碼
3.UI狀態控制StateView的使用
(1)StateView的四種狀態:
//不顯示
public static final int STATE_NO_DATA = 0;
//正在載入
public static final int STATE_LOADING = 1;
//空資料
public static final int STATE_EMPTY = 2;
//沒有網路
public static final int STATE_DISCONNECT=3;
複製程式碼
(2)StateView的使用:
①定義一個通用佈局
<?xml version="1.0" encoding="utf-8"?>
<com.youngmanster.collectionlibrary.base.StateView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"
android:id="@+id/state_view">
</com.youngmanster.collectionlibrary.base.StateView >
複製程式碼
②新增到Ui頁面的layout中
<include layout="@layout/layout_state"/>
複製程式碼
注意:上面的語句新增的layout最外層最好是LinearLayout以及設定為android:orientation="vertical"
③通過以下語句進行狀態切換
stateView.showViewByState(StateView.STATE_LOADING);
stateView.showViewByState(StateView.STATE_EMPTY);
stateView.showViewByState(StateView.STATE_NO_DATA);
stateView.showViewByState(StateView.STATE_DISCONNECT);
複製程式碼
④通過以下語句可以修改不同佈局的內容以及樣式
app:emptyText=""//設定空資料的文字提示
app:emptyImage=""//設定空資料顯示的圖片
app:disConnectImage=""//設定無網路的顯示圖片
app:disConnectText=""//設定無網路的文字提示
app:loadingText=""//設定loading的文字提示
app:loadingViewAnimation=""//設定loading的動畫檔案,只是ImageView
app:tipTextColor=""//設定文字顯示顏色
app:tipTextSize=""//設定文字的大小
複製程式碼
3.三步實現Permission(許可權)設定
(1)設定好要請求的許可權
// 專案的必須許可權,沒有這些許可權會影響專案的正常執行
private static final String[] PERMISSIONS = new String[]{
Manifest.permission.READ_SMS,
Manifest.permission.RECEIVE_WAP_PUSH,
};
複製程式碼
(2)許可權通過PermissionManager管理
PermissionManager permissionManager=PermissionManager.with(this).
//必須許可權
setNecessaryPermissions(PERMISSIONS);
//通過以下語句進行請求
permissionManager.requestPermissions();
複製程式碼
(3)頁面重寫onRequestPermissionsResult
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == PermissionManager.PERMISSION_REQUEST_CODE) {//PERMISSION_REQUEST_CODE為請求許可權的請求值
//有必須許可權選擇了禁止
if (permissionManager.getShouldShowRequestPermissionsCode() == PermissionManager.EXIST_NECESSARY_PERMISSIONS_PROHIBTED) {
showToast("可以在這裡設定重新跳出許可權請求提示框");
} //有必須許可權選擇了禁止不提醒
else if (permissionManager.getShouldShowRequestPermissionsCode() == PermissionManager.EXIST_NECESSARY_PERMISSIONS_PROHIBTED_NOT_REMIND) {
showToast("可以在這裡彈出提示框提示去應用設定頁開啟許可權");
permissionManager.startAppSettings();
}
}
}
複製程式碼
注意:如果有需求先判斷是否所有許可權都已經允許之後再進入主頁面可以通過permissionManager.isLackPermission()進行判斷,如果返回true則進行許可權請求,如果返回false則進入主頁面。
- 多個許可權請求如果其中某一個被禁止提醒,會先把沒有禁止提醒的許可權處理完之後再進行處理。
- 如果是必要許可權被禁止而沒有選擇禁止提醒退出之後下次會重新請求許可權。
- 如果必要許可權被禁止和選擇了禁止提醒重新進入頁面在onRequestPermissionsResult會重新回撥方法。
- 使用者可以根據onRequestPermissionsResult()方法中返回來的標誌PermissionManager.EXIST_NECESSARY_PERMISSIONS_PROHIBTED和PermissionManager.EXIST_NECESSARY_PERMISSIONS_PROHIBTED_NOT_REMIND做出對應的顯示和操作(例如彈框提示跳轉到設定頁面或者toat提示)。
4.提供幾種比較常用的Dialog彈框
①提供的Dialog
- DIALOG_TEXT_TWO_BUTTON_DEFAULT:預設彈窗樣式。
- DIALOG_TEXT_TWO_BUTTON_CUSTOMIZE:自定義彈出按鈕提示。
- DIALOG_LOADING_PROGRASSBAR:預設載入彈框。
- DIALOG_DISPLAY_ADVERTISING:顯示廣告圖的彈框樣式。
- DIALOG_CHOICE_ITEM:單項選擇彈框樣式。
②自定義Dialog樣式
- 繼承BaseDialog,通過setContentView(R.layout.dialog_list);設定彈窗佈局。
- 在提供的initUI()方法中進行相應的邏輯設定。
③BaseDialog提供的方法
- setContentView():設定彈框佈局樣式。
- show():顯示彈框。
- isShowing():判斷彈框是否顯示。
- dismiss():彈框銷燬。
- setCancelable():點選返回鍵和外部不可取消。
- setDialogCancel():點選返回鍵可以取消。
5.提供幾種比較常用的PopupWindow彈框
①BasePopupWindow提供的方法
- BasePopupWindow(Context context) :呼叫該建構函式預設彈出框鋪滿全屏。
- BasePopupWindow(Context context, int w, int h):呼叫該建構函式可指定彈出框大小。
- showPopup():在螢幕中央顯示彈框。
- showPopupAsDropDown(View anchor):在指定控制元件底部顯示彈框。
- setShowMaskView(boolean isShowMaskView):設定是否顯示遮層。
- dismiss():銷燬彈出框。
- getPopupLayoutRes():自定義彈出框的佈局檔案。
- getPopupAnimationStyleRes():自定義彈出框的動畫檔案。
②自定義PopupWindow
-
繼承BasePopupWindow。
-
通過getPopupLayoutRes(R.layout.xxx)設定彈窗佈局。
-
通過getPopupAnimationStyleRes(R.style.xxx)設定彈窗動畫,不需要動畫可以忽略不設定。
<style name="animation_scale" parent="android:Animation.Dialog"> <item name="android:windowEnterAnimation">@anim/scale_tip_in</item> <item name="android:windowExitAnimation">@anim/scale_tip_out</item> </style> 複製程式碼
-
如果需要顯示遮層,在建構函式通過setShowMaskView(true)設定。
6.使用DisplayUtils修改狀態列
- setStatusBarFullTranslucentWithBlackFont(Activity act):狀態列透明黑字。
- setStatusBarBlackFontBgColor(Activity activity,int bgColor):修改狀態列顏色同時字型變為黑色。
- setStatusBarFullTranslucent(Activity act):狀態列透明。
- setStatusBarColor(Activity activity, int colorResId):改變狀態列顏色。
7.提供幾種比較常用的Utils工具類
- DisplayUtils:px和dp的轉換、獲取螢幕高寬、狀態列白底黑字、設定狀態列顏色、設定狀態列全屏透明、獲取狀態列的高度、獲取ActionBar的高度。
- FileUtils:寫檔案、讀取文字檔案中的內容、判斷快取是否失效、檢查檔案是否存在、刪除目錄、檢查是否安裝SD卡、刪除檔案。
- GlideUtils:Glide顯示網路圖片、Glide實現高斯模糊。
- LogUtils:日誌工具類。
- NetworkUtils:網路工具類。
- ToastUtils:Toast提示類。
七、 CustomView的使用
1.CommonTabLayout的使用
①屬性:
- tab_tabIndicatorWidth:設定下滑線的長度。
- tab_tabIndicatorHeight:設定下滑線的高度。
- tab_tabIndicatorColor:下滑線顏色。
- tab_indicator_marginLeft/tab_indicator_marginRight/tab_indicator_marginTop/tab_indicator_marginBottom:設定下滑線外邊距。
- tab_tabTextColor:沒選中字型顏色。
- tab_tabTextSize:字型大小。
- tab_tabSelectedTextColor:選中字型顏色。
- tab_padding:下滑線內邊距,block樣式時可以通過該屬性設定距離。
- tab_tabBackground:Tab的背景顏色。
- tab_indicator_corner:下滑線的圓角大小。
- tab_indicator_gravity(bottom、top):設定下滑線顯示的位置,只針對line和triangle。
- tab_tabMode(scrollable、fixed):Tab的顯示模式。
- tab_indicator_style(line、triangle、block):下滑線的樣式。
②具體使用者可參照例子使用。
2.OutSideFrameTabLayout的使用
①屬性:
- tab_tabIndicatorColor:設定Tab顏色。
- tab_indicator_corner:圓角大小
- tab_indicator_marginLeft/tab_indicator_marginRight/tab_indicator_marginTop/tab_indicator_marginBottom:設定下滑線外邊距。
- tab_tabTextColor:沒選中字型顏色。
- tab_tabTextSize:字型大小。
- tab_tabSelectedTextColor:選中字型顏色。
- tab_padding:內邊距。
- tab_bar_color:bar的背景顏色。
- tab_bar_stroke_color:外框的顏色。
- tab_bar_stroke_width:外框的大小。
- tab_width:bar的長度。