最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Android UI設(shè)計(jì)系列之自定義ViewGroup打造通用的關(guān)閉鍵盤小控件ImeObserverLayout(9)

 更新時(shí)間:2016年06月20日 10:05:37   作者:llew2011  
這篇文章主要介紹了Android UI設(shè)計(jì)系列之自定義ViewGroup打造通用的關(guān)閉鍵盤小控件ImeObserverLayout,具有一定的實(shí)用性和參考價(jià)值,感興趣的小伙伴們可以參考一下

轉(zhuǎn)載請(qǐng)注明出處:http://blog.csdn.net/llew2011/article/details/51598682
我們平時(shí)開發(fā)中總會(huì)遇見一些奇葩的需求,為了實(shí)現(xiàn)這些需求我們往往絞盡腦汁有時(shí)候還茶不思飯不香的,有點(diǎn)夸張了(*^__^*)……我印象最深的一個(gè)需求是在一段文字中對(duì)部分詞語(yǔ)進(jìn)行加粗顯示。當(dāng)時(shí)費(fèi)了不少勁,不過還好,這個(gè)問題最終解決了,有興趣的童靴可以看一下:Android UI設(shè)計(jì)之<六>使用HTML標(biāo)簽,實(shí)現(xiàn)在TextView中對(duì)部分文字進(jìn)行加粗顯示。
之前產(chǎn)品那邊提了這樣的需求:用戶輸入完信息后要求點(diǎn)擊非輸入框時(shí)要把軟鍵盤隱藏。當(dāng)時(shí)看到這個(gè)需求覺得沒啥難度也比較實(shí)際,于是暈暈乎乎的就實(shí)現(xiàn)了,可后來(lái)產(chǎn)品那邊說(shuō)了只要有輸入框的頁(yè)面全都要按照這個(gè)邏輯來(lái),美其名曰用戶體驗(yàn)……當(dāng)時(shí)項(xiàng)目中帶有輸入框的頁(yè)面不少,如果每個(gè)頁(yè)面都寫一遍邏輯,這就嚴(yán)重違背了《重構(gòu),改善既有代碼的設(shè)計(jì)》這本書中的說(shuō)的事不過三原則(事不過三原則說(shuō)的是如果同樣的邏輯代碼如果寫過三遍以上,就要考慮重構(gòu))。于是當(dāng)時(shí)花了點(diǎn)時(shí)間搞了個(gè)通用的輕量級(jí)的關(guān)閉鍵盤的小控件ImeObserverLayout,也是我們今天要講的主角。
開始講解代碼之前我們先看一下Activity的層級(jí)圖,學(xué)習(xí)一下Activity啟動(dòng)之后在屏幕上的視圖結(jié)構(gòu)是怎樣的,要想清楚Activity的顯示層級(jí)視圖最方便的方式是借助Google給我們提供的工具h(yuǎn)ierarchyviewer(該工具位于sdk的tools文件夾下)。hierarchyviewer不僅可以把當(dāng)前正在運(yùn)行的APP的界面視圖層級(jí)顯示出來(lái),而且還可以通過視圖層級(jí)優(yōu)化我們的布局結(jié)構(gòu)。
為了使用hierarchyviewer工具查看當(dāng)前APP的層級(jí)結(jié)構(gòu),我們先做個(gè)簡(jiǎn)單測(cè)試,定義布局文件activity_mian.xml,代碼如下:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent" >

  <TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    android:text="測(cè)試層級(jí)視圖" />

</FrameLayout>

布局文件非常簡(jiǎn)單,根節(jié)點(diǎn)為FrameLayout,中間嵌套了一個(gè)TextView,并讓TextView居中顯示。然后定義MainActivity,代碼如下:

public class MainActivity extends Activity {

 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 }
}


代碼很簡(jiǎn)單,運(yùn)行效果圖如下所示:

        運(yùn)行程序之后我們到sdk的tools文件夾下找到hierarchyviewer,雙擊即可打開,運(yùn)行之后截圖如下:


        hierarchyviewer打開之后,該工具會(huì)列出當(dāng)前手機(jī)可以進(jìn)行視圖層級(jí)展示的所有程序,當(dāng)前正在運(yùn)行的程序會(huì)在列表中以加粗加黑的形式展示。找到我們的程序,雙擊打開,如下圖所示:


上圖就是我們當(dāng)前MainActivity運(yùn)行時(shí)的布局結(jié)構(gòu),左下側(cè)就是結(jié)構(gòu)圖,右側(cè)分別是縮略圖和對(duì)應(yīng)的展示位置圖,這里不再對(duì)工具的具體使用做講解,有興趣的童靴可以自行查閱。根據(jù)結(jié)構(gòu)圖可以發(fā)現(xiàn),當(dāng)前Activity的根視圖是PhoneWindow類下的DercorView,它包含了一個(gè)LinearLayout子視圖,而子視圖LinearLayout下又包含了三個(gè)子視圖,一個(gè)ViewStub和兩個(gè)FragmeLayout,第一個(gè)視圖ViewSub顯示狀態(tài)欄部分,第二個(gè)視圖FrameLayout中包含一個(gè)TextView,這是用來(lái)顯示標(biāo)題的,對(duì)于第三個(gè)視圖FrameLayout,其id是content,這就是我們?cè)贏ctivity中調(diào)用setContentView()方法為當(dāng)前Activity設(shè)置所顯示的View視圖的直接父視圖。

了解了Activity的層級(jí)結(jié)構(gòu)后,可以考慮從層級(jí)結(jié)構(gòu)入手實(shí)現(xiàn)通用的關(guān)閉鍵盤小控件。我們知道在Android體系中事件是層層傳遞的,也就是說(shuō)事件首先傳遞給根視圖DecorView,然后依次往下傳遞并最終傳到目標(biāo)視圖。如果在根視圖DecorView和其子視圖LinearLayout中間添加一個(gè)我們自定義的ViewGroup,那我們就可以在自定義的ViewGroup中對(duì)事件進(jìn)行攔截從而判斷是否關(guān)閉軟鍵盤。

 既然要在DecorView和其子視圖LinearLayout中間添加一個(gè)自定義的ViewGroup就要首先得到DecorView,從上邊Activity的結(jié)構(gòu)圖我們知道調(diào)用Activity的setContentView()給Activity設(shè)置Content時(shí)最終都是添加到id為content的FrameLayout下,所以可以根據(jù)id得到此FrameLayout,然后依次循環(huán)往上找parent,直到找到一個(gè)沒有parent的View,那這個(gè)View就是DecorView。這種方法可行但不是推薦的做法,Google工程師在構(gòu)造Activity的時(shí)候給Activity添加了一個(gè)getWindow()方法,該方法返回一個(gè)代表窗口的Window對(duì)象,該Window類是抽象類,其有一個(gè)方法getDecorView(),看過FrameWork源碼的童靴應(yīng)該清楚該方法返回的就是根視圖DecorView,所以我們采用這種方式。

現(xiàn)在可以獲取到根視圖DecorView了,接下來(lái)就是考慮我們的ViewGroup應(yīng)具備的功能了。首先要實(shí)現(xiàn)點(diǎn)擊輸入框EditText之外的區(qū)域關(guān)閉軟鍵盤就要知道當(dāng)前布局中有哪些EditText,因此自定義的ViewGroup中要有一個(gè)集合,該集合用來(lái)保存當(dāng)前布局文件中的所有的輸入框EditText;其次在什么時(shí)機(jī)查找并保存當(dāng)前布局中的所有輸入框EditText,又在什么時(shí)機(jī)清空保存的輸入框EditText;再次當(dāng)手指點(diǎn)擊屏幕時(shí)可以獲取到點(diǎn)擊的XY坐標(biāo),根據(jù)點(diǎn)擊坐標(biāo)判斷點(diǎn)擊位置是否落在輸入框EditText中從而決定是否關(guān)閉軟鍵盤。

帶著以上問題開始實(shí)現(xiàn)我們的ViewGroup,代碼如下:

public class ImeObserverLayout extends FrameLayout {

 private List<EditText> mEditTexts;
 
 public ImeObserverLayout(Context context) {
 super(context);
 }
 
 public ImeObserverLayout(Context context, AttributeSet attrs) {
 super(context, attrs);
 }
 
 public ImeObserverLayout(Context context, AttributeSet attrs, int defStyleAttr) {
 super(context, attrs, defStyleAttr);
 }
 
 @SuppressLint("NewApi")
 public ImeObserverLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
 super(context, attrs, defStyleAttr, defStyleRes);
 }
 
 @Override
 protected void onAttachedToWindow() {
 super.onAttachedToWindow();
 collectEditText(this);
 }

 @Override
 protected void onDetachedFromWindow() {
 clearEditText();
 super.onDetachedFromWindow();
 }
 
 @Override
 public boolean onInterceptTouchEvent(MotionEvent ev) {
 if(MotionEvent.ACTION_DOWN == ev.getAction() && shouldHideSoftInput(ev)) {
  hideSoftInput();
 }
 return super.onInterceptTouchEvent(ev);
 }
 
 private void collectEditText(View child) {
 if(null == mEditTexts) {
  mEditTexts = new ArrayList<EditText>();
 }
 if(child instanceof ViewGroup) {
  final ViewGroup parent = (ViewGroup) child;
  final int childCount = parent.getChildCount();
  for(int i = 0; i < childCount; i++) {
  View childView = parent.getChildAt(i);
  collectEditText(childView);
  }
 } else if(child instanceof EditText) {
  final EditText editText = (EditText) child;
  if(!mEditTexts.contains(editText)) {
  mEditTexts.add(editText);
  }
 }
 }
 
 private void clearEditText() {
 if(null != mEditTexts) {
  mEditTexts.clear();
  mEditTexts = null;
 }
 }

 private void hideSoftInput() {
 final Context context = getContext().getApplicationContext();
 InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
 imm.hideSoftInputFromWindow(getWindowToken(), 0);
 }

 private boolean shouldHideSoftInput(MotionEvent ev) {
 if(null == mEditTexts || mEditTexts.isEmpty()) {
  return false;
 }
 final int x = (int) ev.getX();
 final int y = (int) ev.getY();
 Rect r = new Rect();
 for(EditText editText : mEditTexts) {
  editText.getGlobalVisibleRect(r);
  if(r.contains(x, y)) {
  return false;
  }
 }
 return true;
 }
} 

ImeObserverLayout繼承了FrameLayout并定義了屬性mEditTexts,mEditTexts用來(lái)保存當(dāng)前頁(yè)面中的所有輸入框EditText。查找所有輸入框EditText的時(shí)機(jī)我們選定了onAttachedToWindow()方法,當(dāng)該View被添加到窗口上后次方法會(huì)被調(diào)用,所以ImeObserverLayout重寫了onAttachedToWindow()方法并在該方法中調(diào)用了collectEditText()方法,我們看一下該方法:

private void collectEditText(View child) {
 if(null == mEditTexts) {
 mEditTexts = new ArrayList<EditText>();
 }
 if(child instanceof ViewGroup) {
 final ViewGroup parent = (ViewGroup) child;
 final int childCount = parent.getChildCount();
 for(int i = 0; i < childCount; i++) {
  View childView = parent.getChildAt(i);
  collectEditText(childView);
 }
 } else if(child instanceof EditText) {
 final EditText editText = (EditText) child;
 if(!mEditTexts.contains(editText)) {
  mEditTexts.add(editText);
 }
 }
}

collectEditText()方法首先對(duì)mEditTexts做了非空校驗(yàn),接著判斷傳遞進(jìn)來(lái)的View是否是ViewGroup類型,如果是ViewGroup類型就循環(huán)其每一個(gè)子View并遞歸調(diào)用collectEditText()方法;如果傳遞進(jìn)來(lái)的是EditText類型,就判斷當(dāng)前集合中是否已經(jīng)保存了該EditText,如果沒有保存就添加。
保存完輸入框EditText之后還要考慮清空的問題,避免發(fā)生內(nèi)存泄漏。所以ImeObserverLayout又重寫了onDetachedFromWindow()方法,然后調(diào)用了clearEditText()方法清空所有的EditText。

private void clearEditText() {
 if(null != mEditTexts) {
 mEditTexts.clear();
 mEditTexts = null;
 }
}

保存了EditText之后就是判斷隱藏軟鍵盤的邏輯了,為了得到點(diǎn)擊坐標(biāo),重寫了onInterceptTouchEvent()方法,如下所示:

 private void clearEditText() {
 if(null != mEditTexts) {
 mEditTexts.clear();
 mEditTexts = null;
 }
} 

在onInterceptTouchEvent()方法中先對(duì)事件做了判斷,如果是DOWN事件并且shouldHideSoftInput()返回true就調(diào)用hideSoftInput()方法隱藏軟鍵盤,我們看一下shouldHideSoftInput()方法,代碼如下:

private boolean shouldHideSoftInput(MotionEvent ev) {
 if(null == mEditTexts || mEditTexts.isEmpty()) {
 return false;
 }
 final int x = (int) ev.getX();
 final int y = (int) ev.getY();
 Rect r = new Rect();
 for(EditText editText : mEditTexts) {
 editText.getGlobalVisibleRect(r);
 if(r.contains(x, y)) {
  return false;
 }
 }
 return true;
}

shouldHideSoftInput()方法首先判斷mEditTexts是否為null或者是否保存有EditText,如果為null或者是空的直接返回false就表示不需要關(guān)閉軟鍵盤,否則循環(huán)遍歷所有的EditText,根據(jù)點(diǎn)擊的XY坐標(biāo)判斷點(diǎn)擊位置是否在EditText區(qū)域內(nèi),如果點(diǎn)擊坐標(biāo)在EditText的區(qū)域內(nèi)直接返回false,否則返回true。
現(xiàn)在我們自定義的ImeObserverLayout準(zhǔn)備就緒,接下來(lái)就是需要把ImeObserverLayout添加到DecorView和其子視圖LinearLayout之間了,為了更方便的使用此控件,我們需要實(shí)現(xiàn)添加的邏輯。
添加邏輯要借助Activity來(lái)獲取根視圖DecorView,所以要把當(dāng)前Activity傳遞進(jìn)來(lái),完整代碼如下所示:

 public final class ImeObserver {
 
 private ImeObserver() {
 }
 
 public static void observer(final Activity activity) {
 if (null == activity) {
  return;
 }
 final View root = activity.getWindow().getDecorView();
 if (root instanceof ViewGroup) {
  final ViewGroup decorView = (ViewGroup) root;
  if (decorView.getChildCount() > 0) {
  final View child = decorView.getChildAt(0);
  decorView.removeAllViews();
  LayoutParams params = child.getLayoutParams();
  ImeObserverLayout observerLayout = new ImeObserverLayout(activity.getApplicationContext());
  observerLayout.addView(child, params);
  LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
  decorView.addView(observerLayout, lp);
  }
 }
 }
 
 private static class ImeObserverLayout extends FrameLayout {

 private List<EditText> mEditTexts;

 public ImeObserverLayout(Context context) {
  super(context);
 }

 public ImeObserverLayout(Context context, AttributeSet attrs) {
  super(context, attrs);
 }

 public ImeObserverLayout(Context context, AttributeSet attrs, int defStyleAttr) {
  super(context, attrs, defStyleAttr);
 }

 @SuppressLint("NewApi")
 public ImeObserverLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
  super(context, attrs, defStyleAttr, defStyleRes);
 }

 @Override
 protected void onAttachedToWindow() {
  super.onAttachedToWindow();
  collectEditText(this);
 }

 @Override
 protected void onDetachedFromWindow() {
  clearEditText();
  super.onDetachedFromWindow();
 }

 @Override
 public boolean onInterceptTouchEvent(MotionEvent ev) {
  if (MotionEvent.ACTION_DOWN == ev.getAction() && shouldHideSoftInput(ev)) {
  hideSoftInput();
  }
  return super.onInterceptTouchEvent(ev);
 }

 private void collectEditText(View child) {
  if (null == mEditTexts) {
  mEditTexts = new ArrayList<EditText>();
  }
  if (child instanceof ViewGroup) {
  final ViewGroup parent = (ViewGroup) child;
  final int childCount = parent.getChildCount();
  for (int i = 0; i < childCount; i++) {
   View childView = parent.getChildAt(i);
   collectEditText(childView);
  }
  } else if (child instanceof EditText) {
  final EditText editText = (EditText) child;
  if (!mEditTexts.contains(editText)) {
   mEditTexts.add(editText);
  }
  }
 }

 private void clearEditText() {
  if (null != mEditTexts) {
  mEditTexts.clear();
  mEditTexts = null;
  }
 }

 private void hideSoftInput() {
  final Context context = getContext().getApplicationContext();
  InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
  imm.hideSoftInputFromWindow(getWindowToken(), 0);
 }

 private boolean shouldHideSoftInput(MotionEvent ev) {
  if (null == mEditTexts || mEditTexts.isEmpty()) {
  return false;
  }
  final int x = (int) ev.getX();
  final int y = (int) ev.getY();
  Rect r = new Rect();
  for (EditText editText : mEditTexts) {
  editText.getGlobalVisibleRect(r);
  if (r.contains(x, y)) {
   return false;
  }
  }
  return true;
 }
 }
}

我們把ImeObserverLayout以內(nèi)部靜態(tài)類的方式放入了ImeObserver中,并設(shè)置了ImeObserverLayout為private的,目的就是不讓外界對(duì)其做操作等,然后給ImeObserver添加了一個(gè)靜態(tài)方法observer(Activity activity),在該方法中把ImeObserverLayout添加進(jìn)了根視圖DecorView和其子視圖LinearLayout中間。
 現(xiàn)在一切就緒,測(cè)試一下看看效果吧,修改MainActivity代碼如下:

public class MainActivity extends Activity {

 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_ime);
 ImeObserver.observer(this);
 }
}

MainActivity的代碼不需要改動(dòng),只是在setContentView()方法后添加了ImeObserver.observer(this)這一行代碼就實(shí)現(xiàn)了關(guān)閉輸入框的功能,是不是很輕量級(jí)并且集成很方便?(*^__^*) ……
我們運(yùn)行一下程序,效果如下: 

恩,看效果感覺還不錯(cuò),該控件本身并沒有什么技術(shù)含量,就是要求對(duì)Activity的層級(jí)結(jié)構(gòu)圖比較熟悉,然后清楚事件傳遞機(jī)制,最后可以根據(jù)坐標(biāo)來(lái)判斷點(diǎn)擊位置從而決定是否關(guān)閉軟鍵盤。
好了,自定義ViewGroup,打造自己通用的關(guān)閉軟鍵盤控件到這里就告一段落了,感謝收看……

相關(guān)文章

  • Android入門之在Activity之間穿梭的Intent

    Android入門之在Activity之間穿梭的Intent

    Intent可以用來(lái)啟動(dòng)Activity(startActivity(Intent))、Serveice(startService(Intent))等組件,可以用來(lái)綁定Activity和Service以建立它們之間的通信(bindServiceConnaction(Intent,ServiceConnection,int)),可以作為Broadcast Intent發(fā)送給廣播接收器
    2021-10-10
  • Android10.0實(shí)現(xiàn)本地音樂播放(附源碼下載)

    Android10.0實(shí)現(xiàn)本地音樂播放(附源碼下載)

    這篇文章主要介紹了Android10.0實(shí)現(xiàn)本地音樂播放(附源碼下載),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-06-06
  • Android原生視頻播放VideoView的使用

    Android原生視頻播放VideoView的使用

    這篇文章主要為大家詳細(xì)介紹了Android原生視頻播放VideoView的使用,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2020-05-05
  • Android實(shí)現(xiàn)文件按時(shí)間先后順序排列顯示的示例代碼

    Android實(shí)現(xiàn)文件按時(shí)間先后順序排列顯示的示例代碼

    在很多Android應(yīng)用中,需要管理和展示本地文件,對(duì)文件按最后修改時(shí)間排序展示,能讓用戶直觀地了解文件的創(chuàng)建或修改順序,從而更方便地查找最新或最舊的文件,本文將介紹如何在Android平臺(tái)上獲取指定目錄下的文件列表,并按照時(shí)間先后排序,需要的朋友可以參考下
    2025-04-04
  • Android手機(jī)衛(wèi)士之綁定sim卡序列號(hào)

    Android手機(jī)衛(wèi)士之綁定sim卡序列號(hào)

    這篇文章主要介紹了Android手機(jī)衛(wèi)士之綁定sim卡序列號(hào)的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2016-10-10
  • 在kotlin的安卓項(xiàng)目中使用dagger的操作方法

    在kotlin的安卓項(xiàng)目中使用dagger的操作方法

    在 Kotlin 的 Android 項(xiàng)目中使用 ??Dagger??(特別是 ??Dagger Hilt??,官方推薦的簡(jiǎn)化版)進(jìn)行依賴注入(DI)可以大幅提升代碼的可測(cè)試性和模塊化程度,這篇文章主要介紹了在kotlin的安卓項(xiàng)目中使用dagger的相關(guān)操作,需要的朋友可以參考下
    2025-04-04
  • android銀行卡號(hào)驗(yàn)證算法詳解

    android銀行卡號(hào)驗(yàn)證算法詳解

    這篇文章主要介紹了android銀行卡號(hào)的驗(yàn)證算法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-09-09
  • Android簡(jiǎn)單實(shí)現(xiàn)自定義流式布局的方法

    Android簡(jiǎn)單實(shí)現(xiàn)自定義流式布局的方法

    這篇文章主要介紹了Android簡(jiǎn)單實(shí)現(xiàn)自定義流式布局的方法,結(jié)合實(shí)例形式分析了Android流式布局的原理與實(shí)現(xiàn)技巧,需要的朋友可以參考下
    2016-07-07
  • Android 極光推送別名與標(biāo)簽方式

    Android 極光推送別名與標(biāo)簽方式

    這篇文章主要介紹了Android 極光推送別名與標(biāo)簽方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來(lái)看看吧
    2020-03-03
  • Android自定義豎排TextView實(shí)現(xiàn)實(shí)例

    Android自定義豎排TextView實(shí)現(xiàn)實(shí)例

    這篇文章主要介紹了Android自定義豎排TextView實(shí)現(xiàn)實(shí)例的相關(guān)資料,需要的朋友可以參考下
    2017-05-05

最新評(píng)論

南开区| 乌拉特中旗| 赣榆县| 嘉荫县| 泰和县| 公主岭市| 靖宇县| 黄浦区| 遵化市| 北海市| 沙坪坝区| 桐庐县| 新化县| 梨树县| 宜州市| 房产| 泌阳县| 台东市| 青浦区| 吴桥县| 宿松县| 郸城县| 卓尼县| 澄城县| 交城县| 灵石县| 定襄县| 吐鲁番市| 通许县| 资溪县| 凉城县| 梁平县| 永修县| 嫩江县| 恭城| 博野县| 隆子县| 沈丘县| 阆中市| 闽侯县| 台中市|