編輯:關於Android編程
最近看了一些android的源碼,發現設計模式無處不在啊!感覺有點亂,於是決定要把設計模式好好梳理一下,於是有了這篇文章。
作用:
保證在Java應用程序中,一個類Class只有一個實例存在。
好處:
由於單例模式在內存中只有一個實例,減少了內存開銷。
單例模式可以避免對資源的多重占用,例如一個寫文件時,由於只有一個實例存在內存中,避免對同一個資源文件的同時寫操作。
單例模式可以再系統設置全局的訪問點,優化和共享資源訪問。
使用情況:
建立目錄 數據庫連接的單線程操作
某個需要被頻繁訪問的實例對象
第一種形式:
public class Singleton {
/* 持有私有靜態實例,防止被引用,此處賦值為null,目的是實現延遲加載 */
private static Singleton instance = null;
/* 私有構造方法,防止被實例化 */
private Singleton() {
}
/* 懶漢式:第一次調用時初始Singleton,以後就不用再生成了
靜態方法,創建實例 */
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
但是這有一個問題,不同步啊!在對據庫對象進行的頻繁讀寫操作時,不同步問題就大了。
第二種形式:
既然不同步那就給getInstance方法加個鎖呗!我們知道使用synchronized關鍵字可以同步方法和同步代碼塊,所以:
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
或是
public static Singleton getInstance() {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
return instance;
}
獲取Singleton實例:
Singleton.getInstance().方法()
軟鍵盤管理的 InputMethodManager
源碼(以下的源碼都是5.1的):
205 public final class InputMethodManager {
//.........
211 static InputMethodManager sInstance;
//.........
619 public static InputMethodManager getInstance() {
620 synchronized (InputMethodManager.class) {
621 if (sInstance == null) {
622 IBinder b = ServiceManager.getService(Context.INPUT_METHOD_SERVICE);
623 IInputMethodManager service = IInputMethodManager.Stub.asInterface(b);
624 sInstance = new InputMethodManager(service, Looper.getMainLooper());
625 }
626 return sInstance;
627 }
628 }
使用的是第二種同步代碼塊的單例模式(可能涉及到多線程),類似的還有
AccessibilityManager(View獲得點擊、焦點、文字改變等事件的分發管理,對整個系統的調試、問題定位等)
BluetoothOppManager等。
當然也有同步方法的單例實現,比如:CalendarDatabaseHelper
307 public static synchronized CalendarDatabaseHelper getInstance(Context context) {
308 if (sSingleton == null) {
309 sSingleton = new CalendarDatabaseHelper(context);
310 }
311 return sSingleton;
312 }
定義一個用於創建對象的接口,讓子類決定實例化哪一個類。工廠方法使一個類的實例化延遲到其子類。
對同一個接口的實現類進行管理和實例化創建
<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPrzZyejO0sPH09DV4tH50ru49tDox/OjujwvcD4NCjxwPravzu9BbmltYWyjrMv809DQ0M6qbW92ZSgpoaPT0MG9uPbKtc/WwOBjYXS6zWRvZ6GjzqrBy82z0ru53MDtus20tL2oztLDx8novMbSu7j2uaSzp8Sjyr2hozxiciAvPg0KoaGhoc2syrHBvbj219PA4NPQuPfX1LXE0NDOqqOsQ2F009BlYXRGaXNoKCmjrERvZ9PQZWF0Qm9uZSgpLjwvcD4NCjxwPr3hubnNvKO6PGJyIC8+DQo8aW1nIGFsdD0="這裡寫圖片描述" src="/uploadfile/Collfiles/20160623/20160623091545613.png" title="\" />
Animal接口:
interface animal {
void move();
}
Cat類:
public class Cat implements Animal{
@Override
public void move() {
// TODO Auto-generated method stub
System.out.println("我是只肥貓,不愛動");
}
public void eatFish() {
System.out.println("愛吃魚");
}
}
Dog類:
public class Dog implements Animal{
@Override
public void move() {
// TODO Auto-generated method stub
System.out.println("我是狗,跑的快");
}
public void eatBone() {
System.out.println("愛吃骨頭");
}
}
那麼現在就可以建一個工廠類(Factory.java)來對實例類進行管理和創建了.
public class Factory {
//靜態工廠方法
//多處調用,不需要實例工廠類
public static Cat produceCat() {
return new Cat();
}
public static Dog produceDog() {
return new Dog();
}
//當然也可以一個方法,通過傳入參數,switch實現
}
使用:
Animal cat = Factory.produceCat();
cat.move();
//-----------------------------
Dog dog = Factory.produceDog();
dog.move();
dog.eatBone();
工廠模式在業界運用十分廣泛,如果都用new來生成對象,隨著項目的擴展,animal還可以生出許多其他兒子來,當然兒子還有兒子,同時也避免不了對以前代碼的修改(比如加入後來生出兒子的實例),怎麼管理,想著就是一團糟。
Animal cat = Factory.produceCat();
這裡實例化了Animal但不涉及到Animal的具體子類(減少了它們之間的偶合聯系性),達到封裝效果,也就減少錯誤修改的機會。
Java面向對象的原則,封裝(Encapsulation)和分派(Delegation)告訴我們:具體事情做得越多,越容易范錯誤,
一般來說,這樣的普通工廠就可以滿足基本需求。但是我們如果要新增一個Animal的實現類panda,那麼必然要在工廠類裡新增了一個生產panda的方法。就違背了 閉包的設計原則(對擴展要開放對修改要關閉) ,於是有了抽象工廠模式。
抽象工廠模式提供一個創建一系列相關或相互依賴對象的接口,而無需指定它們具體的類。
啥意思?就是把生產抽象成一個接口,每個實例類都對應一個工廠類(普通工廠只有一個工廠類),同時所有工廠類都繼承這個生產接口。
生產接口Provider:
interface Provider {
Animal produce();
}
每個產品都有自己的工廠
CatFactory:
public class CatFactory implements Provider{
@Override
public Animal produce() {
// TODO Auto-generated method stub
return new Cat();
}
}
DogFactory:
public class DogFactory implements Provider{
@Override
public Animal produce() {
// TODO Auto-generated method stub
return new Dog();
}
}
產品生產:
Provider provider = new CatFactory();
Animal cat =provider.produce();
cat.move();
現在我們要加入panda,直接新建一個pandaFactory就行了,這樣我們系統就非常靈活,具備了動態擴展功能。
比如AsyncTask的抽象工廠實現:
工廠的抽象:
59 public interface ThreadFactory {
//省略為備注
69 Thread newThread(Runnable r);
70 }
產品的抽象(new Runnable就是其實現類):
56 public interface Runnable {
//省略為備注
68 public abstract void run();
69 }
AsyncTask中工廠類的實現:
185 private static final ThreadFactory sThreadFactory = new ThreadFactory() {
186 private final AtomicInteger mCount = new AtomicInteger(1);
187
188 public Thread newThread(Runnable r) {
189 return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
190 }
191 };
我們可以創建另外類似的工廠,生產某種專門的線程(多線程),非常容易擴展。
當然,android中的應用還有很多(比如BitmapFactory),有興趣的小伙伴可以去扒一扒。
將一個類的接口轉換成客戶希望的另外一個接口。
我們經常碰到要將兩個沒有關系的類組合在一起使用,第一解決方案是:修改各自類的接口,但是如果我們沒有源代碼,或者,我們不願意為了一個應用而修改各自的接口。 怎麼辦?
使用Adapter,在這兩種接口之間創建一個混合接口。
模式中的角色
需要適配的類(Adaptee):需要適配的類。
適配器(Adapter):通過包裝一個需要適配的對象,把原接口轉換成目標接口。
目標接口(Target):客戶所期待的接口。可以是具體的或抽象的類,也可以是接口。
// 需要適配的類
class Adaptee {
public void specificRequest() {
System.out.println("需要適配的類");
}
}
// 目標接口
interface Target {
public void request();
}
實現方式:
①、對象適配器(采用對象組合方式實現)
// 適配器類實現標准接口
class Adapter implements Target{
// 直接關聯被適配類
private Adaptee adaptee;
// 可以通過構造函數傳入具體需要適配的被適配類對象
public Adapter (Adaptee adaptee) {
this.adaptee = adaptee;
}
public void request() {
// 這裡是使用委托的方式完成特殊功能
this.adaptee.specificRequest();
}
}
// 測試類
public class Client {
public static void main(String[] args) {
// 需要先創建一個被適配類的對象作為參數
Target adapter = new Adapter(new Adaptee());
adapter.request();
}
}
如果Target不是接口而是一個具體的類的情況,這裡的Adapter直接繼承Target就可以了。
②、類的適配器模式(采用繼承實現)
// 適配器類繼承了被適配類同時實現標准接口
class Adapter extends Adaptee implements Target{
public void request() {
super.specificRequest();
}
}
// 測試類
public static void main(String[] args) {
// 使用適配類
Target adapter = new Adapter();
adapter.request();
}
如果Target和 Adaptee都是接口,並且都有實現類。 可以通過Adapter實現兩個接口來完成適配。
還有一種叫PluggableAdapters,可以動態的獲取幾個adapters中一個。使用Reflection技術,可以動態的發現類中的Public方法。
優點
系統需要使用現有的類,而此類的接口不符合系統的需要。那麼通過適配器模式就可以讓這些功能得到更好的復用。
將目標類和適配者類解耦,通過引入一個適配器類重用現有的適配者類,而無需修改原有代碼,更好的擴展性。
缺點
過多的使用適配器,會讓系統非常零亂,不易整體進行把握。比如,明明看到調用的是A接口,其實內部被適配成了B接口的實現。如果不是必要,不要使用適配器,而是直接對系統進行重構。
android中的Adapter就有很多了,這個大家都經常用。
Adapter是AdapterView視圖與數據之間的橋梁,Adapter提供對數據的訪問,也負責為每一項數據產生一個對應的View。
Adapter的繼承結構
BaseAdapter的部分源碼:
30public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter {
31 private final DataSetObservable mDataSetObservable = new DataSetObservable();
32
33 public boolean hasStableIds() {
34 return false;
35 }
36
37 public void registerDataSetObserver(DataSetObserver observer) {
38 mDataSetObservable.registerObserver(observer);
39 }
40
41 public void unregisterDataSetObserver(DataSetObserver observer) {
42 mDataSetObservable.unregisterObserver(observer);
43 }
ListAdapter, SpinnerAdapter都是Target ,數據是Adaptee ,采用對象組合方式。
使多個對象都有機會處理請求,從而避免請求的發送者和接收者之間的耦合關系。將這些對象連成一條鏈,並沿著這條鏈傳遞該請求,直到有一個對象處理它為止。
發出這個請求的客戶端並不知道鏈上的哪一個對象最終處理這個請求,這使得系統可以在不影響客戶端的情況下動態地重新組織和分配責任。
編程中的小體現:
if(a<10){
...
}
else if (a<20){
...
}
else if(a<30){
...
}
else{
...
}
程序必須依次掃描每個分支進行判斷,找到對應的分支進行處理。
責任鏈模式的優點
可以降低系統的耦合度(請求者與處理者代碼分離),簡化對象的相互連接,同時增強給對象指派職責的靈活性,增加新的請求處理類也很方便;
責任鏈模式的缺點
不能保證請求一定被接收,且對於比較長的職責鏈,請求的處理可能涉及到多個處理對象,系統性能將受到一定影響,而且在進行代碼調試時不太方便。
每次都是從鏈頭開始,這也正是鏈表的缺點。
觸摸、按鍵等各種事件的傳遞
有興趣的可以看一下這篇文章View事件分發機制源碼分析,我這就不多說了。
有時被稱作發布/訂閱模式,觀察者模式定義了一種一對多的依賴關系,讓多個觀察者對象同時監聽某一個主題對象。這個主題對象在狀態發生變化時,會通知所有觀察者對象,使它們能夠自動更新自己。
將一個系統分割成一個一些類相互協作的類有一個不好的副作用,那就是需要維護相關對象間的一致性。我們不希望為了維持一致性而使各類緊密耦合,這樣會給維護、擴展和重用都帶來不便。觀察者就是解決這類的耦合關系的(依賴關系並未完全解除,抽象通知者依舊依賴抽象的觀察者。)。
觀察者模式的組成
①抽象主題(Subject)
它把所有觀察者對象的引用保存到一個聚集裡,每個主題都可以有任何數量的觀察者。抽象主題提供一個接口,可以增加和刪除觀察者對象。
②具體主題(ConcreteSubject)
將有關狀態存入具體觀察者對象;在具體主題內部狀態改變時,給所有登記過的觀察者發出通知。
③抽象觀察者(Observer)
為所有的具體觀察者定義一個接口,在得到主題通知時更新自己。
④具體觀察者(ConcreteObserver)
實現抽象觀察者角色所要求的更新接口,以便使本身的狀態與主題狀態協調。
言語蒼白,上代碼:
//抽象觀察者
public interface Observer
{
public void update(String str);
}
//具體觀察者
public class ConcreteObserver implements Observer{
@Override
public void update(String str) {
// TODO Auto-generated method stub
System.out.println(str);
}
}
//抽象主題
public interface Subject
{
public void addObserver(Observer observer);
public void removeObserver(Observer observer);
public void notifyObservers(String str);
}
//具體主題
public class ConcreteSubject implements Subject{
// 存放觀察者
private List list = new ArrayList();
@Override
public void addObserver(Observer observer) {
// TODO Auto-generated method stub
list.add(observer);
}
@Override
public void removeObserver(Observer observer) {
// TODO Auto-generated method stub
list.remove(observer);
}
@Override
public void notifyObservers(String str) {
// TODO Auto-generated method stub
for(Observer observer:list){
observer.update(str);
}
}
}
下面是測試類:
/**
* @author fanrunqi
*/
public class Test {
public static void main(String[] args) {
//一個主題
ConcreteSubject eatSubject = new ConcreteSubject();
//兩個觀察者
ConcreteObserver personOne = new ConcreteObserver();
ConcreteObserver personTwo = new ConcreteObserver();
//觀察者訂閱主題
eatSubject.addObserver(personOne);
eatSubject.addObserver(personTwo);
//通知開飯了
eatSubject.notifyObservers("開飯啦");
}
}
“關於代碼你有什麼想說的?”
“沒有,都在代碼裡了”
“(⊙o⊙)哦.....”
觀察者模式在android中運用的也比較多,最熟悉的ContentObserver。
① 抽象類ContentResolver中(Subject)
注冊觀察者:
1567 public final void registerContentObserver(Uri uri, boolean notifyForDescendents,
1568 ContentObserver observer, int userHandle)
取消觀察者:
1583 public final void unregisterContentObserver(ContentObserver observer)
抽象類ContentObserver中(Observer)
94 public void onChange(boolean selfChange) {
95 // Do nothing. Subclass should override.
96 }
144 public void onChange(boolean selfChange, Uri uri, int userId) {
145 onChange(selfChange, uri);
146 }
129 public void onChange(boolean selfChange, Uri uri) {
130 onChange(selfChange);
131 }
觀察特定Uri引起的數據庫的變化,繼而做一些相應的處理(最終都調用的第一個函數).
② DataSetObserver,其實這個我們一直在用,只是沒意識到。
我們再看到BaseAdapter的部分源碼:
37 public void registerDataSetObserver(DataSetObserver observer) {
38 mDataSetObservable.registerObserver(observer);
39 }
40
41 public void unregisterDataSetObserver(DataSetObserver observer) {
42 mDataSetObservable.unregisterObserver(observer);
43 }
上面兩個方法分別向向BaseAdater注冊、注銷一個DataSetObserver實例。
DataSetObserver 的源碼:
24 public abstract class DataSetObserver {
29 public void onChanged() {
30 // Do nothing
31 }
38 public void onInvalidated() {
39 // Do nothing
40 }
41}
DataSetObserver就是一個觀察者,它一旦發現BaseAdapter內部數據有變量,就會通過回調方法DataSetObserver.onChanged和DataSetObserver.onInvalidated來通知DataSetObserver的實現類。
建造者模式:是將一個復雜的對象的構建與它的表示分離(同構建不同表示),使得同樣的構建過程可以創建不同的表示。
一個人活到70歲以上,都會經歷這樣的幾個階段:嬰兒,少年,青年,中年,老年。並且每個人在各個階段肯定是不一樣的,世界上不存在兩個人在人生的這5個階段的生活完全一樣,但是活到70歲以上的人,都經歷了這幾個階段是肯定的。實際上這是一個比較經典的建造者模式的例子了。
將復雜的內部創建封裝在內部,對於外部調用的人來說,只需要傳入建造者和建造工具,對於內部是如何建造成成品的,調用者無需關心。
建造者模式通常包括下面幾個角色:
① Builder:一個抽象接口,用來規范產品對象的各個組成成分的建造。
② ConcreteBuilder:實現Builder接口,針對不同的商業邏輯,具體化復雜對象的各部分的創建,在建造過程完成後,提供產品的實例。
③ Director:指導者,調用具體建造者來創建復雜對象的各個部分,不涉及具體產品的信息,只負責保證對象各部分完整創建或按某種順序創建。
④ Product:要創建的復雜對象。
與抽象工廠的區別:在建造者模式裡,有個指導者,由指導者來管理建造者,用戶是與指導者聯系的,指導者聯系建造者最後得到產品。即建造模式可以強制實行一種分步驟進行的建造過程。
Product和產品的部分Part接口
public interface Product { }
public interface Part { }
Builder:
public interface Builder {
void buildPartOne();
void buildPartTwo();
Product getProduct();
}
ConcreteBuilder:
//具體建造工具
public class ConcreteBuilder implements Builder {
Part partOne, partTwo;
public void buildPartOne() {
//具體構建代碼
};
public void buildPartTwo() {
//具體構建代碼
};
public Product getProduct() {
//返回最後組裝的產品
};
}
Director :
public class Director {
private Builder builder;
public Director( Builder builder ) {
this.builder = builder;
}
public void construct() {
builder.buildPartOne();
builder.buildPartTwo();
}
}
建造:
ConcreteBuilder builder = new ConcreteBuilder();
Director director = new Director(builder);
//開始各部分建造
director.construct();
Product product = builder.getResult();
優點:
客戶端不必知道產品內部組成的細節。
具體的建造者類之間是相互獨立的,對系統的擴展非常有利。
由於具體的建造者是獨立的,因此可以對建造過程逐步細化,而不對其他的模塊產生任何影響。
使用場合:
創建一些復雜的對象時,這些對象的內部組成構件間的建造順序是穩定的,但是對象的內部組成構件面臨著復雜的變化。
要創建的復雜對象的算法,獨立於該對象的組成部分,也獨立於組成部分的裝配方法時。
android中的Dialog就使用了Builder Pattern,下面來看看AlertDialog的部分源碼。
371 public static class Builder {
372 private final AlertController.AlertParams P;
373 private int mTheme;
393 public Builder(Context context, int theme) {
394 P = new AlertController.AlertParams(new ContextThemeWrapper(
395 context, resolveDialogTheme(context, theme)));
396 mTheme = theme;
397 }
AlertDialog的Builder 是一個靜態內部類,沒有定義Builder 的抽象接口。
對AlertDialog設置的屬性會保存在Build類的成員變量P(AlertController.AlertParams)中。
Builder類中部分方法:
416 public Builder setTitle(int titleId) {
417 P.mTitle = P.mContext.getText(titleId);
418 return this;
419 }
452 public Builder setMessage(int messageId) {
453 P.mMessage = P.mContext.getText(messageId);
454 return this;
455 }
525 public Builder setPositiveButton(CharSequence text, final OnClickListener listener) {
526 P.mPositiveButtonText = text;
527 P.mPositiveButtonListener = listener;
528 return this;
529 }
而show()方法會返回一個結合上面設置的dialog實例
991 public AlertDialog show() {
992 AlertDialog dialog = create();
993 dialog.show();
994 return dialog;
995 }
996 }
997
998}
972 public AlertDialog create() {
973 final AlertDialog dialog = new AlertDialog(P.mContext, mTheme, false);
974 P.apply(dialog.mAlert);
975 dialog.setCancelable(P.mCancelable);
976 if (P.mCancelable) {
977 dialog.setCanceledOnTouchOutside(true);
978 }
979 dialog.setOnCancelListener(P.mOnCancelListener);
980 dialog.setOnDismissListener(P.mOnDismissListener);
981 if (P.mOnKeyListener != null) {
982 dialog.setOnKeyListener(P.mOnKeyListener);
983 }
984 return dialog;
985 }
簡單建造:
new AlertDialog.Builder(context)
.setTitle("標題")
.setMessage("消息框")
.setPositiveButton("確定", null)
.show();
備忘錄模式又叫做快照模式(Snapshot Pattern)或Token模式,是對象的行為模式。
備忘錄對象是一個用來存儲另外一個對象內部狀態的快照的對象。備忘錄模式的用意是在不破壞封裝的條件下,將一個對象的狀態捕捉(Capture)住,並外部化,存儲起來,從而可以在將來合適的時候把這個對象還原到存儲起來的狀態。備忘錄模式常常與命令模式和迭代子模式一同使用。
備忘錄模式所涉及的角色有三個:
① Originator(發起人): 負責創建一個備忘錄Memento,用以記錄當前時刻它的內部狀態,並可使用備忘錄恢復內部狀態。Originator可根據需要決定Memento存儲Originator的哪些內部狀態。
② Memento(備忘錄): 負責存儲Originnator對象的內部狀態,並可防止Originator以外的其他對象訪問備忘錄Memento,備忘錄有兩個接口,Caretaker只能看到備忘錄的窄接口,它只能將備忘錄傳遞給其他對象。
③、 Caretaker(管理者):負責保存好備忘錄Memento,不能對備忘錄的內容進行操作或檢查。
public class Originator {
private String state;
/**
* 工廠方法,返回一個新的備忘錄對象
*/
public Memento createMemento(){
return new Memento(state);
}
/**
* 將發起人恢復到備忘錄對象所記載的狀態
*/
public void restoreMemento(Memento memento){
this.state = memento.getState();
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
System.out.println("當前狀態:" + this.state);
}
}
public class Memento {
private String state;
public Memento(String state){
this.state = state;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
}
public class Caretaker {
private Memento memento;
/**
* 備忘錄的取值方法
*/
public Memento retrieveMemento(){
return this.memento;
}
/**
* 備忘錄的賦值方法
*/
public void saveMemento(Memento memento){
this.memento = memento;
}
}
使用:
Originator o = new Originator();
Caretaker c = new Caretaker();
//改變負責人對象的狀態
o.setState("On");
//創建備忘錄對象,並將發起人對象的狀態儲存起來
c.saveMemento(o.createMemento());
//修改發起人的狀態
o.setState("Off");
//恢復發起人對象的狀態
o.restoreMemento(c.retrieveMemento());
不需要了解對象的內部結構的情況下備份對象的狀態,方便以後恢復。
Activity的onSaveInstanceState和onRestoreInstanceState就是通過Bundle(相當於備忘錄對象)這種序列化的數據結構來存儲Activity的狀態,至於其中存儲的數據結構,這兩個方法不用關心。
還是看一下源碼:
1365 protected void onSaveInstanceState(Bundle outState) {
1366 outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState());
1367 Parcelable p = mFragments.saveAllState();
1368 if (p != null) {
1369 outState.putParcelable(FRAGMENTS_TAG, p);
1370 }
1371 getApplication().dispatchActivitySaveInstanceState(this, outState);
1372 }
1019 protected void onRestoreInstanceState(Bundle savedInstanceState) {
1020 if (mWindow != null) {
1021 Bundle windowState = savedInstanceState.getBundle(WINDOW_HIERARCHY_TAG);
1022 if (windowState != null) {
1023 mWindow.restoreHierarchyState(windowState);
1024 }
1025 }
1026 }
當然還有其他的很多設計模式沒有說明,以後有時間在繼續整理。
那麼問題來了,什麼是設計模式?
設計模式是前輩、大牛在實際編程中對遇到的問題解決方案的抽象。
介紹Action Bar是一種新増的導航欄功能,在Android 3.0之後加入到系統的API當中,它標識了用戶當前操作界面的位置,並提供了額外的用戶動作、界面導航等功能
本文講述的是Android中RelativeLayout、FrameLayout的用法。具體如下:RelativeLayout是一個按照相對位置排列的布局,跟Absolu
1、 概述 DialogFragment在android 3.0時被引入。是一種特殊的Fragment,用於在Activity的內容之上展示一個模態的對
因項目緣故需重新定制SwitchButton,效果如下: 過程如下:1.圓角矩形的繪制2.字體繪制3.小圓繪制4.左右滑動動畫效果繪制代碼如下: package