編輯:關於Android編程
可能很多人並不知道Dragger2是什麼,有什麼用,為什麼這個開源庫會這麼的熱門。
所以,在使用Dragger2之前,我們先要了解一些設計模式,看完之後想必你會喜歡上這個庫。
A. 高層次的模塊不應該依賴於低層次的模塊,他們都應該依賴於抽象。
B. 抽象不應該依賴於具體實現,具體實現應該依賴於抽象。
這裡我會再用一個更符合我們Android開發者的例子來說明。
產品經理要求我們寫一個下拉刷新功能,要和QQ的一模一樣。
goole百度無果(假設)之後,我們只能自己動手。我們可能會這麼寫:
/** * Created by 13797 on 2016/8/19. * 可下拉刷新的ListView */ public class RefreshListView extends ListView { private QQHeader qqHeader; public RefreshListView(Context context) { this(context, null); } public RefreshListView(Context context, AttributeSet attrs) { super(context, attrs); qqHeader = new QQHeader(context); } @Override public boolean onTouchEvent(MotionEvent ev) { int action = ev.getAction(); int y = (int) ev.getRawY(); switch (action) { case MotionEvent.ACTION_DOWN: qqHeader.onPress(y); break; case MotionEvent.ACTION_MOVE: qqHeader.onMove(y); break; case MotionEvent.ACTION_UP: qqHeader.onRelease(); break; } return super.onTouchEvent(ev); } }
/** * Created by 13797 on 2016/8/19. *
* 高仿QQ的下拉刷新頭部 */ public class QQHeader extends LinearLayout { private static final int REFRESH_HEIGHT = 200; private int downY; public QQHeader(Context context) { super(context); } public void onPress(int y) { // 手指按下,記錄當前手指位置 downY = y; } public void onMove(int y) { // 手指移動,改變header的高度 ViewGroup.LayoutParams lp = getLayoutParams(); lp.height = y - downY; setLayoutParams(lp); } public void onRelease() { // 手指抬起,判斷header高度是否大於刷新高度,開始刷新或者回到頂部 if (getHeight() > REFRESH_HEIGHT) { // 開始刷新 } else { // 回到頂部 } } }
問題1:誰依賴誰?
RefreshListView依賴於QQHeader
因為RefreshListView持有QQHeader的引用,並且需要QQHeader才能實現下拉刷新的功能。
可能有人不是很明白,那我再舉個例子。
小車和汽油,誰依賴誰?
很清晰的可以得出,小車依賴於汽油,而不是汽油依賴於小車。汽油還可以用到摩托車,飛機上……
而QQHeader同樣可以用到RecyclerView或者ScrollView上……
那麼我麼繼續往下說
下拉刷新功能寫的很不錯,產品經理覺得很滿意。
但是過了兩天,產品經理跟我們說:我還是覺得像美團那種帶動畫效果的比較好看。
然後我們又默默的去修改代碼, RefreshListView改成了這樣的:
/** * Created by 13797 on 2016/8/19. * 可下拉刷新的ListView */ public class RefreshListView extends ListView { private MeituanHeader meituanHeader; public RefreshListView(Context context) { super(context); } public RefreshListView(Context context, AttributeSet attrs) { super(context, attrs); meituanHeader = new MeituanHeader(context); } @Override public boolean onTouchEvent(MotionEvent ev) { int action = ev.getAction(); int y = (int) ev.getRawY(); switch (action) { case MotionEvent.ACTION_DOWN: meituanHeader.onPress(y); break; case MotionEvent.ACTION_MOVE: meituanHeader.onMove(y); break; case MotionEvent.ACTION_UP: meituanHeader.onRelease(); break; } return super.onTouchEvent(ev); } }
產品經理覺得很好看,興高采烈的找老板去了,留下你怨念的眼神。
我們作為一個有經驗的程序員,為了防止產品經理又需要改需求,我們需要提高一下控件的擴展性了。
然後我們分析如下:
RefreshListView已經完成了必要事件分發邏輯,功能上是沒問題的。
但是由於RefreshListView依賴於具體的Header編程,因此在更換新的Header時都不得不修改RefreshListView的源碼,我們可以通過引入抽象的Header來解決該問題。
修改後如下:
/** * Created by 13797 on 2016/8/19. * 可下拉刷新的ListView */ public class RefreshListView extends ListView { private RefreshHeader refreshHeader; public RefreshListView(Context context) { super(context); } public RefreshListView(Context context, AttributeSet attrs) { super(context, attrs); refreshHeader = new MeituanHeader(context); // refreshHeader = new QQHeader(context); } @Override public boolean onTouchEvent(MotionEvent ev) { int action = ev.getAction(); int y = (int) ev.getRawY(); switch (action) { case MotionEvent.ACTION_DOWN: refreshHeader.onPress(y); break; case MotionEvent.ACTION_MOVE: refreshHeader.onMove(y); break; case MotionEvent.ACTION_UP: refreshHeader.onRelease(); break; } return super.onTouchEvent(ev); } }
/** * Created by 13797 on 2016/8/20. */ public interface RefreshHeader { void onPress(int y); void onMove(int y); void onRelease(); }
/** * Created by 13797 on 2016/8/19. * 下拉刷新頭部 */ public class QQHeader extends LinearLayout implements RefreshHeader { private static final int REFRESH_HEIGHT = 200; private int downY; public QQHeader(Context context) { super(context); } @Override public void onPress(int y) { // 手指按下,記錄當前手指位置 downY = y; } @Override public void onMove(int y) { // 手指移動,改變header的高度 ViewGroup.LayoutParams lp = getLayoutParams(); lp.height = y - downY; setLayoutParams(lp); } @Override public void onRelease() { // 手指抬起,判斷header高度是否大於刷新高度,開始刷新或者回到頂部 if (getHeight() > REFRESH_HEIGHT) { // 開始刷新 } else { // 回到頂部 } } }
// 美團Header的就不貼出來了,都是偽代碼 public class MeituanHeader extends LinearLayout implements RefreshHeader { }
可以看到,引入接口之後,RefreshListView針對RefreshHeader編程。在更換Header的時候我們只需要修改構造中refreshHeader = new MeituanHeader(context);這行代碼即可。
RefreshListView不依賴於QQHeader或者MeituanHeader,而是依賴於RefreshHeader這個抽象(接口)。
而QQHeader和MeituanHeader同樣依賴於RefreshHeader這個抽象。
這就是依賴倒置原則
抽象不應該依賴於具體實現,具體實現應該依賴於抽象。
抽象不應該依賴於具體,具體應該依賴於抽象。
既然了解了依賴倒置原則,那麼這裡就再說一點,為什麼MVP模式中,View需要使用接口,而Presenter則不需要,或者說沒有必要?
根據依賴倒置原則,高層次的模塊不應該依賴於低層次的模塊,他們都應該依賴於抽象
MVP中,Presenter屬於高層次模塊,而View是低層次,所以View就需要實現接口了,就是這麼簡單……
想象一下這種情況:
如果Presenter中拿到的是Activity對象,而不是一個接IView口。
那麼Presenter就可以隨意調用Activity中的方法了,如果真調用了activity的方法,Presenter和Activity就會產生很強的耦合,那麼你就無法單純的使用junit進行單元測試了。
還有就是,如果你想將Activity換成Fragment或者View時,你也需要大量的修改Presenter了。
二、依賴注入
大篇幅的說完依賴倒置之後,那麼什麼是依賴注入呢?
首先我們看RefreshListVIew, 我們在它初始化的時候一起初始化了RefreshHeader。
public RefreshListView(Context context, AttributeSet attrs) {
super(context, attrs);
refreshHeader = new MeituanHeader(context);
// refreshHeader = new QQHeader(context);
}
這樣使得RefreshListView不僅依賴於RefreshHeader這個接口,還依賴於MeituanHeader這個實現,當我們想更換header的時候就必須去修改RefreshListView的這段源碼了。
那麼我們可以換另外一種方式實現:
不在RefreshListView初始化的時候一起初始化Header,而是通過setRefreshHeader的方式提供依賴。
當我們想更換Header的時候就不需要修改RefreshListView了。也可以更加方便的定制各個不同的RefreshListView,比如產品說:這個頁面用QQHeader 那個頁面用meituanHeader……
public RefreshListView(Context context, AttributeSet attrs) {
super(context, attrs);
// refreshHeader = new MeituanHeader(context);
// refreshHeader = new QQHeader(context);
}
public void setRefreshHeader(RefreshHeader refreshHeader) {
this.refreshHeader = refreshHeader;
}
而這就是我們說的依賴注入了,其實我們開發中經常用到這樣的依賴注入,但是我們從來沒有注意到這件事。因為代碼敲多了,總會自然而然的貼近原則
以簡單的話來說就是
依賴注入是不在類中實例化其他依賴的類,而是先把依賴的類實例化了,然後以參數的方式傳入構造函數中,
讓上層模塊和依賴進一步解耦。
以上我們用的是setter注入
依賴注入的方式還有另外兩種,一是構造器注入。
比如:
public RefreshListView(Context context, AttributeSet attrs, RefreshHeader refreshHeader ) {
super(context, attrs);
this.refreshHeader = refreshHeader;
// refreshHeader = new MeituanHeader(context);
// refreshHeader = new QQHeader(context);
}
但是很明顯這裡不能使用……因為RefreshListView是在Xml文件中配置,由Android系統自動生成。所以我們采用Setter注入的方式。
另外還有一種方式就是接口注入,我個人並不認同這是一種方式。
不多解釋,看看維基百科吧,裡面都是代碼,不會英文估計都是能看懂的。
https://en.wikipedia.org/wiki/Dependency_injection
另外還有一點就是Injector
故名思意,就是注入器,負責初始化依賴,並且將依賴注入到上層模塊中。
很明顯上面的下拉刷新例子中,Activity或者Fragment扮演著這個角色。
三、關於原則我已經說完了……
dragger2並不是我要介紹的重點,雖然我有心要寫,但是我發現有很多文章已經寫的很好很好了。
本菜開源的一個自己寫的Demo,希望能給Androider們有所幫助,水平有限,見諒見諒.. https://github.com/zhiaixinyang/MyFir
項目地址:https://github.com/wlkdb/GA_network_info點擊打開鏈接1、整個app分為android客戶端、java服務端和數據層,客戶
簡介: 一般我們在自定義ViewGroup 的時候會通常都會用到onInterceptTouchEvent ,onTouchEvent 這些方法去進行距離的判斷然後利用s
Android Studio在Android Monitor中包含了一個logcat的tab,可以打印系統事件,比如垃圾回收發生時,實時打印應用消息。為了顯示需要的信息,