前面一篇文章 《android studio的入門使用》
已經(jīng)講了如何導(dǎo)入一個(gè)開源的項(xiàng)目乘盖。本文則來講如何使用這些開源的資源,以及在一個(gè)小app的開發(fā)中遇到的問題憔涉。因?yàn)椴豢赡苊婷婢愕蕉┛颍誀幦∮米詈喢鳎ㄋ椎姆绞絹頂⑹觥?/p>
層級的劃分
我們寫一個(gè)稍微復(fù)雜的App的時(shí)候兜叨,不可能只有一個(gè)包穿扳,必定分門別類。而分類的標(biāo)準(zhǔn)国旷,大部分應(yīng)該是遵循這三類view(UI層)矛物,bussiness(邏輯處理層),還有data(數(shù)據(jù)層)议街。然后我們根據(jù)這個(gè)標(biāo)準(zhǔn)把他們放在不同的包里面泽谨。這樣一來,結(jié)構(gòu),邏輯都很清晰吧雹。例如這個(gè)項(xiàng)目里面:
包名 | 用途 |
---|---|
Base | 包含一些基類 |
domain | 存放一些javabean的類 |
Fragment | 顧名思義存放fragment的類 |
Global | 存放網(wǎng)絡(luò)的全局類 |
Utils | 工具類 |
View | 改寫后的布局類 |
它們分工明確骨杂,內(nèi)容一目了然。每個(gè)人的習(xí)慣都不盡相同雄卷,找到自己熟悉的命名方式搓蚪,結(jié)構(gòu)明了就好。
- 命名方式
關(guān)于命名的規(guī)范
1.對于類名我們使用:UpperCamelCase風(fēng)格編寫
2.對于方法名我們使用:LowerCamelCase風(fēng)格編寫
這兩個(gè)都是駝峰命名丁鹉,一個(gè)是大駝峰(每個(gè)單詞的首字母大寫)妒潭,一個(gè)是小駝峰(除第一個(gè)單詞以外的單詞首字母大寫)。這種方式命名美觀揣钦,簡潔雳灾。我縱觀了一下整個(gè)項(xiàng)目代碼,發(fā)現(xiàn)有些時(shí)候不是按照上面的規(guī)則冯凹,而是例如fragment_left_menu這樣的下劃線命名谎亩,所以其實(shí)并沒有唯一的標(biāo)準(zhǔn),只是可以的話盡量這樣命名我們的代碼宇姚。
- 基類的創(chuàng)建
顧名思義匈庭,基類,即為初始類浑劳。如果兩個(gè)以上的類同時(shí)擁有相似的特征阱持,我們就把它抽取出來單獨(dú)形成一個(gè)類。就好比我們在一個(gè)類的兩個(gè)不同的函數(shù)中魔熏,需要使用同一個(gè)參數(shù)衷咽,這時(shí)我們也會(huì)習(xí)慣性的把它變成全局的變量供兩個(gè)函數(shù)使用。
假如我們現(xiàn)在要寫5個(gè)頁面道逗,顯然界面各不相同兵罢,但是我們要為它們編寫一個(gè)共同的基類献烦。我們發(fā)現(xiàn)無論頁面怎么變滓窍,它的標(biāo)題欄是大同小異的。
標(biāo)題欄大家很熟悉巩那,ImageButton和TextView,下面我們不知道是什么吏夯,就用一個(gè)幀布局Framelayout代替。以后如果要添加一個(gè)不同的布局即横,它有一個(gè)方法addview()便搞定了
上面的代碼噪生,只是一個(gè)模板,給大家提供思路东囚,我們需要初始化界面跺嗽,初始化數(shù)據(jù),mMainActivity的作用除了在Inflate函數(shù)里充當(dāng)上下文的參數(shù)(應(yīng)該改為public類型),其實(shí)在對象的流動(dòng)傳遞里也有很大的作用桨嫁,后面再敘述植兰。除此之外如果你有必要,還可以來個(gè)類的初始化public BaseTab(){},又或是把view抽取出來當(dāng)作公共參數(shù)璃吧,又或者你必須規(guī)定子類實(shí)現(xiàn)某個(gè)方法楣导,某個(gè)函數(shù),那就把整個(gè)基類寫成抽象類畜挨。
開源項(xiàng)目的使用
我們隨便百度筒繁,都可以搜索到一些很熱門的,很好用的開源項(xiàng)目巴元。當(dāng)然還是像上一篇說的毡咏,你可以直接在github下載下來,然后關(guān)聯(lián)到你的項(xiàng)目逮刨,具體做法上一篇已經(jīng)講了血当,現(xiàn)在我就假設(shè)你已經(jīng)導(dǎo)入好了。
- Sliding Menu
因?yàn)槭褂眠^程中禀忆,有牽扯到Fragment臊旭,所以就一起講了。
sliding menu顧名思義側(cè)邊欄箩退,最直觀的如圖片所示(網(wǎng)絡(luò)圖):
public class MainActivity extends SlidingFragmentActivity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
/* 都聲明為framelayout */
setContentView(R.layout.mainactiviy);
//設(shè)置側(cè)邊欄
setBehindContentView(R.layout.left_menu);
//獲取側(cè)邊欄對象
SlidingMenu slidingMenu=getSlidingMenu();
//設(shè)置全屏拖動(dòng)
slidingMenu.setTouchModeAbove(SlidingMenu.TOUCHMODE_FULLSCREEN);
//設(shè)置屏幕的預(yù)留寬度
slidingMenu.setBehindOffset(400);
initFragment();
}
現(xiàn)在結(jié)合代碼說下最簡單的用法离熏,我們首先繼承SlidingFragmentActivity,然后我們?yōu)橹黜撁婧蛡?cè)邊欄都設(shè)置一個(gè)xml的布局戴涝,這兩個(gè)布局都只有一個(gè) framelayout 方便我們之后換成其他的布局滋戳,我們的主頁面和側(cè)邊欄的頁面都用fragment實(shí)現(xiàn),等下再說啥刻。
接著我們要獲取側(cè)邊欄對象奸鸯,它有個(gè)方法getSlidingMenu(),用as的朋友可帽,直接再quick fix一下(Alt+Enter),對象就出來了娄涩。我們?yōu)樗O(shè)置參數(shù):
方法 | 用途 |
---|---|
setBehindContentView | 設(shè)置側(cè)邊欄的布局文件 |
方法 | 用途 |
---|---|
setTouchModeAbove | 設(shè)置拖動(dòng)的位置 有全屏 不設(shè)置 左邊 右邊等 |
方法 | 用途 |
---|---|
setBehindOffset | 設(shè)置屏幕的預(yù)留寬度 其實(shí)也就是變向設(shè)置你拖出來的大小 |
方法 | 用途 |
---|---|
toggle | 隱藏側(cè)邊欄 |
接下來我們要?jiǎng)?chuàng)建兩個(gè)Fragment去填補(bǔ)上面的framelayout。
- Fragment
記得以前學(xué)習(xí)Fragment的時(shí)候映跟,一頭霧水蓄拣,分為什么靜態(tài)和動(dòng)態(tài)。但隨著時(shí)間推移努隙,慢慢的就清晰很多了球恤。所以當(dāng)你遇到一時(shí)理解不了的問題,放一放荸镊,過一陣子再回頭看咽斧,會(huì)明白許多堪置。
我們現(xiàn)在只講如何動(dòng)態(tài)創(chuàng)建Fragment:
大致流程應(yīng)該是這樣
1.我們的Activity 的布局文件只有一個(gè)幀布局
2.創(chuàng)建管理者
3.開啟事物
4.替換
5.提交事務(wù)
具體代碼如下:
private void initFragment(){
//管理者
FragmentManager fm = getSupportFragmentManager();
//開啟事物
FragmentTransaction transaction = fm.beginTransaction();
//替換
transaction.replace(R.id.fl_left_menu,new Fragment_left_menu(),FRAGMENT_LEFT_MENU);
transaction.replace(R.id.fl_content, new Fragment_content(), FRAGMENT_CONTENT);
//提交
transaction.commit();}
我們在替換這個(gè)函數(shù)里面,3個(gè)參數(shù)分別代表:replace(對應(yīng)framelayout的id张惹,要替換的類晋柱,Tag標(biāo)簽)
如果你只是一個(gè)fragment類,寫完替換進(jìn)去就搞定了诵叁,現(xiàn)在我們這個(gè)是兩個(gè)Fragment,我們可以學(xué)以致用雁竞,寫一個(gè)BaseFragment基類繼承fragment。
到此我們已經(jīng)動(dòng)態(tài)創(chuàng)建完了拧额,那設(shè)置Tag有什么用呢碑诉。我們設(shè)置標(biāo)簽,當(dāng)然是為了獲取它的對象侥锦。假如我們想獲取側(cè)邊欄的Fragment對象进栽,我們可以這樣做:
private static final String FRAGMENT_LEFT_MENU ="fragment_left_menu" ;
我們在開頭聲明一個(gè)靜態(tài)的變量
這樣我們在替換的時(shí)候就設(shè)置好了他們對應(yīng)的tag值
最后只要寫一個(gè)函數(shù),通過管理者就可以輕松獲得Fragment對象了:
public Fragment_left_menu getleftMenuFragment(){
//管理者
FragmentManager fm = getSupportFragmentManager();
Fragment_left_menu fragment = (Fragment_left_menu) fm.findFragmentByTag(FRAGMENT_LEFT_MENU);
return fragment;
}
官方是非常推薦我們使用fragment的恭垦,所以大家趕緊學(xué)起來吧快毛。就好像以前大家都不習(xí)慣寫Viewholder,現(xiàn)在也被強(qiáng)迫著寫了番挺。最后附贈(zèng)一個(gè)思維導(dǎo)圖幫助大家理解它:
- xutils
工具包:提供一些方便的工具為我們使用,有四大模塊:DbUtils唠帝,ViewUtils,HttpUtils玄柏,BitmapUtils
1.注釋
為了解決頻繁的findviewbyid襟衰,于是就有了這個(gè)ViewUtils(其實(shí)好像也沒快多少)
用法(來自官方文檔):
//在Activity中注入:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
ViewUtils.inject(this);
//注入view和事件 ...
textView.setText("some text...");
...
}
//在Fragment中注入:
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.bitmap_fragment, container, false);
// 加載fragment布局
ViewUtils.inject(this, view);
//注入view和事件 ...
}
最后只需在你需要UI綁定或者事件綁定的語句前加個(gè)注釋即可如:
@ViewInject(R.id.textView)
TextView textView;
@OnClick(R.id.test_button)
public void testButtonClick(View v) {
...}
2.網(wǎng)絡(luò)工具
我們可以通過HttpUtils請求網(wǎng)絡(luò),方便的獲取網(wǎng)絡(luò)數(shù)據(jù)粪摘。
以解析json為例瀑晒,你可以這樣做:
private void getDatafromServer() {
HttpUtils httpUtils=new HttpUtils();
httpUtils.send(HttpRequest.HttpMethod.GET, murl, new RequestCallBack<String>()
{
public void onSuccess(ResponseInfo<String> responseInfo) {
String result = responseInfo.result;
System.out.println("頁簽結(jié)果是" + result);
parsedata(result);
}
public void onFailure(HttpException error, String msg) {
Toast.makeText(mActivity, msg,Toast.LENGTH_SHORT).show();
error.printStackTrace();
}
});
}
我們首先創(chuàng)建一個(gè)HttpUtils對象,在send方法里面徘意,有三個(gè)參數(shù)分別是(請求的方法(get,post),地址苔悦,放回的類型),在匿名內(nèi)部類中有兩個(gè)函數(shù):一個(gè)是成功,獲得數(shù)據(jù)后椎咧,我們可以讓它進(jìn)一步對數(shù)據(jù)解析玖详。一個(gè)是失敗,我們可以讓他在logcat里打印錯(cuò)誤日志并toast出問題在哪邑退。
這個(gè)時(shí)候我們假設(shè)已成功獲取了json數(shù)據(jù)竹宋,接著對它進(jìn)行解析:
protected void parsedata(String result) {
Gson gson=new Gson();
tabDetaildata = gson.fromJson(result,Tabdata.class);
....
}
我們借助gson的fromjson方法傳入剛獲得的結(jié)果和一個(gè)javabean類,就可以成功解析json數(shù)據(jù)了地技。
注意:getDatafromServer()這個(gè)方法是運(yùn)行在主線程的,所以在里面的方法當(dāng)然可以隨意對UI進(jìn)行設(shè)置
3.解析網(wǎng)絡(luò)圖片
我們以前常使用線程秒拔,異步加載的方式下載網(wǎng)絡(luò)圖片莫矗,還要擔(dān)心內(nèi)存溢出等問題,現(xiàn)在只需使用BitmapUtils幾行代碼就可以決解這樣復(fù)雜的問題。
以listview里加載網(wǎng)絡(luò)圖片為例:
/* 新聞列表的適配器 */
class NewslistAdapter extends BaseAdapter{
private BitmapUtils utils;
public NewslistAdapter(){
utils=new BitmapUtils(mActivity);
utils.configDefaultLoadingImage(R.mipmap.pic_item_list_default);
}
···
}
我們在內(nèi)部初始化的時(shí)候傳入當(dāng)前上下文(mActivity)聲明一個(gè)全局的BitmapUtils對象作谚,其中configDefaultLoadingImage方法是用來預(yù)加載圖片三娩。
utils.display(hodler.listIcon,tabnewsData.listimage);
最后在適配器的getview函數(shù)中,對UI進(jìn)行設(shè)置妹懒。它有個(gè)display()函數(shù)分別填入view和要添加的圖片數(shù)據(jù)雀监,這樣就大功告成了。
- ViewpagerIndicator
1.我們首先介紹的是viewpage的導(dǎo)航指示器:TabPageIndicator眨唬,比較直觀的如圖所示(網(wǎng)絡(luò)圖):
如圖会前,我們先把TabPageIndicator的路徑拷貝下來,在所在viewpage的xml上添加對應(yīng)的布局:
<com.viewpagerindicator.TabPageIndicator
android:id="@+id/indicator"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
最后匾竿,只需在初始view的時(shí)候瓦宜,綁定一下UI
myIndicator= (TabPageIndicator)view.findViewById(R.id.indicator);
并且在viewpager添加完適配器之后,指示器再綁定viewpager
//添加適配器
mviewPager.setAdapter(new MenudetailAdapter());
viewpagermyIndicator.setViewPager(mviewPager);
還有一點(diǎn)需要注意的是岭妖,如果指示器和viewpager綁定以后临庇,如果viewpager要設(shè)置監(jiān)聽事件,這時(shí)我們應(yīng)該對指示器設(shè)置而不是viewpager自身昵慌。
2.CirclePageIndicator
有時(shí)我們翻閱圖片假夺,為了有個(gè)指示的效果:
這時(shí)我們就用到CirclePageIndicator
和上面的一樣,我們首先在布局文件中將其添加
我們看到里面有自定義控件的屬性app,所以首先當(dāng)然把命名空間給引入斋攀,在頭文件的地方添加
xmlns:app="http://schemas.android.com/apk/res-auto"
接著初始化侄泽,然后綁定viewpager,和上面一樣蜻韭。它有兩個(gè)方法值得提一下悼尾, setSnap()填入(true,false)表示點(diǎn)是否跳躍,也就是我們上方展示圖片的效果肖方。onPageSelected()選擇初始停留的頁面闺魏。
- 事件的攔截機(jī)制
為了便于理解,我畫了一個(gè)思維導(dǎo)圖
由Activity把事件分發(fā)出去->到了最外層子控件->最外層子控件通過dispatchTouchevent接收分發(fā)的事件->傳給onInterceptTouchevent,由它決定是否攔截俯画,若攔截就交給onTouchevent對事件進(jìn)行處理析桥,若不攔截就繼續(xù)傳到內(nèi)層的子控件->若所有子控件都不處理,則返回給activity處理艰垂。
我們現(xiàn)在回到實(shí)際的項(xiàng)目中泡仗,來討論事件在具體的例子中如何實(shí)現(xiàn)攔截:
這個(gè)app的界面中,最左邊有個(gè)側(cè)邊欄(Sliding menu)猜憎,最外面是一個(gè)由tab來切換的viewpager1娩怎,第二層是由viewpagerIndicator來切換的viewpager2,最里面的那層是一個(gè)圖片切換的Viewpager3。這應(yīng)該是一個(gè)比較復(fù)雜的事件攔截的案例了胰柑,我們再來畫一個(gè)清晰的圖:
我們常用到事件攔截截亦,是在重寫一個(gè)屬于我們自己的自定義view的時(shí)候爬泥。例如在寫最外層的Viewpager1的時(shí)候,我們希望它沒有滾動(dòng)這個(gè)功能崩瓤,而是通過tab來控制頁面的翻閱袍啡,而且我們希望它不攔截事件,而是讓內(nèi)層的viewpager可以接受到事件却桶,就可以在重寫的viewpager里這樣做:
//停止?jié)L動(dòng)
public boolean onTouchEvent(MotionEvent ev) { return false;}
//事件是否攔截
public boolean onInterceptTouchEvent(MotionEvent ev) { return false;}
在寫viewpager2的時(shí)候境输,遇到了問題,我們發(fā)現(xiàn)如果我們往右劃的時(shí)候颖系,slidingmenu這個(gè)父控件會(huì)攔截我們的右劃事件嗅剖。所以這個(gè)時(shí)候我們必須重寫一個(gè)viewpager類,來規(guī)定事件的攔截集晚。
//請求父類不攔截觸摸事件
public boolean dispatchTouchEvent(MotionEvent ev) {
if(getCurrentItem()!=0) {
getParent().requestDisallowInterceptTouchEvent(true);
}else {
getParent().requestDisallowInterceptTouchEvent(false);
}
return super.dispatchTouchEvent(ev);
}
我們在事件分發(fā)的這個(gè)方法中窗悯,調(diào)用requestDisallowInterceptTouchEvent這個(gè)方法,來請求父類不攔截觸摸事件偷拔,所以當(dāng)當(dāng)前的頁面不是第一頁的時(shí)候蒋院,我們就不攔截,否則我們攔截莲绰。
之后我們寫到viewpager3欺旧, 我們希望可以自由的劃動(dòng)里面的圖片, 所以不希望父類攔截我們的事件蛤签,在重寫的viewpager里:
public boolean dispatchTouchEvent(MotionEvent ev) {
getParent().requestDisallowInterceptTouchEvent(true);
return super.dispatchTouchEvent(ev);
}
可是問題就來了辞友,你這里不希望被攔截,但是它的父類viewpager2在第一個(gè)頁簽往右劃的時(shí)候會(huì)規(guī)定會(huì)被sliding menu攔截震肮,所以我們發(fā)現(xiàn)viewpager3在第一個(gè)頁簽往右劃的時(shí)候還是會(huì)引出sliding menu称龙,而不是往前面翻圖片。出現(xiàn)了矛盾戳晌,這里我們就需要對代碼進(jìn)行重構(gòu):我們不給viewpage2重寫類鲫尊,而是給它設(shè)置監(jiān)聽(setOnPageChangeListener)
public void onPageSelected(int position) {
//判斷頁面 控制slidingmenu是否劃出
MainActivity mainActivity= (MainActivity) mActivity;
SlidingMenu slidingMenu = mainActivity.getSlidingMenu();
if(position==0){
slidingMenu.setTouchModeAbove(slidingMenu.TOUCHMODE_FULLSCREEN);
}else{
slidingMenu.setTouchModeAbove(slidingMenu.TOUCHMODE_NONE);
}
}
viewpager3的重寫類,我們也做出新的事件攔截規(guī)定沦偎,我們規(guī)定
1.第一個(gè)圖片往右滑動(dòng) 跳到上一個(gè)頁簽(或側(cè)邊欄)
2.最后一個(gè)圖片往左滑動(dòng) 跳到下一個(gè)頁簽
3.向下劃的時(shí)候
需要攔截疫向,于是:
public boolean dispatchTouchEvent(MotionEvent ev) {
switch(ev.getAction()){
case MotionEvent.ACTION_DOWN:
//先讓父類不攔截子類的事件
getParent().requestDisallowInterceptTouchEvent(true);
//獲取子類坐標(biāo)
startX = (int) ev.getRawX();
startY = (int) ev.getRawY();
break;
case MotionEvent.ACTION_MOVE:
int endX= (int) ev.getRawX();
int endY = (int) ev.getRawY();
//開始分類作比較 左劃 右劃 下劃
if(Math.abs(endX-startX)>Math.abs(endY-startY)){
//左右劃
if(endX>startX){
//右劃 第一個(gè)頁簽 就攔截
if(getCurrentItem()==0){
getParent().requestDisallowInterceptTouchEvent(false);
}
}
else{//左劃
if(getCurrentItem()==getAdapter().getCount()-1){
getParent().requestDisallowInterceptTouchEvent(false); }
}
}else{
//下劃 攔截
getParent().requestDisallowInterceptTouchEvent(false);
}
break;
default:
break;
}
return super.dispatchTouchEvent(ev);
}
到此我們就管理好了這個(gè)復(fù)雜的事件攔截家族
- 傳遞
我們在開頭的時(shí)候,說到mActivity是充當(dāng)當(dāng)前上下文的作用豪嚎,除此之外我們還可以拿它來傳遞對象搔驼。比如我們要在后面的頁簽類里,寫個(gè)函數(shù)控制sliding menu的拖出與隱藏侈询,我們前面說過這個(gè)函數(shù)是toggle舌涨。但是我們必須獲得一個(gè)是sliding menu的對象,但是要獲得sliding menu對象妄荔,我們首先得獲得MainAcitvity的對象泼菌。于是這里就要我們靈活的進(jìn)行對象的傳遞:
private void toggleSlidingMenu() {
//獲得slidingmenu對象
MainActivity mainActivity= (MainActivity) mActiviy;
SlidingMenu slidingMenu = mainActivity.getSlidingMenu();
slidingMenu.toggle();
//切換狀態(tài)
}
對于數(shù)據(jù)的傳遞谍肤,每個(gè)項(xiàng)目都有可能錯(cuò)綜復(fù)雜啦租,在android studio里有個(gè)很方便的快捷鍵哗伯,幫助我們找到哪些地方用到了這些數(shù)據(jù)。
選中要查找的目標(biāo)篷角,按住alt+F7(find usages):
這個(gè)時(shí)候我們還可以右鍵:
跳轉(zhuǎn)到引用的頁面焊刹,迅速幫助我們定位,這樣我們就不會(huì)暈頭轉(zhuǎn)向了恳蹲。
以上就是我重點(diǎn)要講的虐块,如有不足請多多包涵,也希望大家可以支持我嘉蕾,謝謝贺奠!