編輯:關於Android編程
NavigationBar可以手動隱藏,隨著華為榮耀手機有了這個特點後,目前有很多android手機都有該特性。如下截圖所示:
上圖的底部虛擬導航按鈕的左、右邊有兩個按鈕點擊這個按鈕,虛擬按鈕就會消失,當從屏幕底部向上滑動時候,虛擬按鈕就就會出現,這個消失和出現的過程中整個屏幕的布局都會從新計算。
接下來,分下面幾個點來說一下具體實現的效果。
在systemui這個app裡面有一個類PhoneStatusBar,在這個類被創建的時候會判斷當前系統是否定義了虛擬按鈕(NavigationBar),如果定義了就添加,否則不添加。源碼如下。
判斷是否有定義虛擬按鈕(NavigationBar):
boolean showNav = mWindowManagerService.hasNavigationBar();創建虛擬按鈕(NavigationBar)的代碼:
int layoutId = R.layout.navigation_bar; if(RecentsActivity.FLOAT_WINDOW_SUPPORT){ layoutId = R.layout.navigation_bar_float_window; } mNavigationBarView = (NavigationBarView) View.inflate(context, /*R.layout.navigation_bar*/layoutId, null);很顯然mNavigationBarView這個view就是顯示虛擬按鈕(NavigationBar)的。
最後在systemui的這個類PhoneStatusBar裡面通過如下方法把mNavigationBarView顯示出來(添加到系統中):
mWindowManager.addView(mNavigationBarView, getNavigationBarLayoutParams());隱藏的方法就可以通過mWindowManager來removeView方法來實現。實驗結果是:這是一個不錯的選擇。
具體如何實現呢?首先通過上面的mNavigationBarView的布局文件(R.layout.navigation_bar)可以知道,我們可以重寫這個布局文件,在重寫的布局文件中添加兩個隱藏按鈕,這個很簡單。其次還有個問題,就是在systemui裡面隱藏了虛擬按鈕(NavigationBar,如何通知WindowManager,這個可以通過定義一個新的KEYCODE,單點擊這個隱藏按鈕時候發出這個特殊的KEYCODE的按鍵事件。
(二):如何實現從底部滑動時候能夠顯示已經隱藏的虛擬按鈕(NavigationBar),當然在橫屏的時候,是從左、右邊緣滑動來顯示已經隱藏的虛擬按(NavigationBar) 通過對源碼的分析發現,在PhoneWindowManager這個類裡面可以找到蛛絲馬跡。
在包 com.android.internal.policy.impl裡面有PhoneWindowManager類,這是一個主要管理手機顯示系統的所有window的類,我們知道在android中每個顯示的界面其實都是一個window,比如一個activity的顯示界面、一個Dialog彈出框、還有上面提到的虛擬按鈕(NavigationBar)、包括下拉狀態欄、已經鎖屏界面等等,通過hierarchyviewer.bat這個工具就可以看到當前手機正在顯示的所有window.
當然PhoneWindowManager類還處理一些其他的事情,比如按鍵事件的處理(按home回到待機界面)、鎖屏的觸發等等,不過我覺得其實都是管理的是window的切換。
在這個PhoneWindowManager類裡面,你會發現一個有用的對象:mSystemGestures。從名字可以發現這是一個系統手勢的類。沒錯,你能夠從手機屏幕頂部下滑拉出系統狀態欄就是靠他了。
我們就需要修改這個類,使得手機可以發現我們從下往上滑動的手勢(從左、右邊邊緣滑動),這樣就可以在 PhoneWindowManager裡面收到這個我們需要的手勢了。
這樣一來,在PhoneWindowManager裡面收到顯示虛擬按鈕(NavigationBar)的手勢,就想辦法去顯示虛擬按鈕(NavigationBar)就可以了。在PhoneWindowManager中要顯示虛擬按鈕(NavigationBar)的辦法和顯示下拉狀態欄類似,你需要在systemui裡面定義相應的方法,然後在PhoneWindowManager裡面通過如下代碼獲取StatusBarService:
IStatusBarService statusbar = getStatusBarService();
最後通過StatusBarService來調用在PhoneStatusBar裡面定義的顯示虛擬按鈕(NavigationBar)的方法。
如下是源碼:
PhoneWindowManager顯示虛擬按鈕(NavigationBar)的函數:
private void showNavigationBar(final int type){ if(isKeyguardLocked()){ return; } startToShowNavbar = true; //Slog.i("yu_PhoneWindowManager", "showNavigationBar: NavigationBarMoveType="+NavigationBarMoveType); NavigationBarMoveType = type; if(mNavigationBar == null) { Slog.i("yu_PhoneWindowManager", "RAMOS showNavigationBar: showNavigationBar"); //Log.v("NavigationGuard", "RAMOS showNavigationBar startToShowNavbar="+startToShowNavbar); mHandler.post(new Runnable() { @Override public void run() { try { IStatusBarService statusbar = getStatusBarService(); if (statusbar != null) { //Slog.i("yu_PhoneWindowManager", "showNavigationBar"); statusbar.showNavigationBar(type); } } catch (RemoteException e) { // re-acquire status bar service next time it is needed. mStatusBarService = null; } } }); } }注意:在PhoneWindowManager類裡面可以通過判斷這個對象mNavigationBar是否為空來確認虛擬按鈕(NavigationBar)是否被隱藏。
其他的showNavigationBar方法的聲明和定義你只需仿照hideRecentApps來做就可以了。
最後在PhoneStatusBar裡面實現具體的虛擬按鈕(NavigationBar)顯示即可。
比如,下面是我的實現方法:
@Override // CommandQueue public void showNavigationBar(int type) { //Log.i("way", TAG + " showNavigationBar..."); Log.i("yu_PhoneStatusBar", "showNavigationBar type="+type); if (mNavigationBarView != null) { try { mWindowManagerService.StartToShowNavbar(type); } catch (RemoteException ex) { } return; } if(mTempNavigationBarView == null){ makeNewNavigationBar(); } if(mTempNavigationBarView != null){ mNavigationBarView = mTempNavigationBarView; //mNavigationBarView.setVisibility(View.VISIBLE); mWindowManager.addView(mNavigationBarView, mNavigationBarLayoutParams); prepareNavigationBarView(true); mNavigationBarView.setDisabledFlags(mDisabled); //mNavigationBarView.reorient(); //mNavigationBarView.notifyScreenOn(true); mTempNavigationBarView = null; }else{ Log.i("yu_PhoneStatusBar", "showNavigationBar: ERROR"); } }重要是這這一個語句:mWindowManager.addView(mNavigationBarView, mNavigationBarLayoutParams);
注意:每次通過mWindowManager來removeView掉NavigationBarView後,這個剛剛被remove的NavigationBarView是不能再次利用的,下次還使用這個NavigationBarView會報錯。
(三)如何實現,在設置裡面去配置虛擬按鈕(NavigationBar)的排序。如下圖:
要解決這個問題,需要修改下面三個地方:
1:在frameworks/base/core/java/android/provider/Settings.java裡面添加如此代碼:
public static final String RAMOS_NAVBAR_STYLE = "RAMOS_NAVBAR_STYLE";
我們就可以通過RAMOS_NAVBAR_STYLE 來保存我們虛擬按鈕(NavigationBar)配置的排序了。
2:在設置中app中,添加一個設置的界面重寫SettingsPreferenceFragment來實現,配置自己的Preferences的xml文件,其中的RadioPreferences需要自己重寫CheckBoxPreference來完成:
如下是我寫的CheckBoxPreference的RamosRadioNavbarStylePreference關鍵代碼:
public RamosRadioNavbarStylePreference(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); setWidgetLayoutResource(R.layout.preference_widget_radiobutton); }
void setOnClickListener(OnClickListener listener) { mListener = listener; } @Override public void onClick() { if (mListener != null) { mListener.onRadioButtonClicked(this); } } @Override protected void onBindView(View view) { super.onBindView(view); mViewGroup = view; TextView title = (TextView) view.findViewById(android.R.id.title); if (title != null) { title.setSingleLine(false); title.setMaxLines(3); } UpdateNavbarStyle(view); }
public void setNavbarStyle(int style){ if(style > -1 && mNavbarStyle != style){ mNavbarStyle = style; notifyChanged();//UpdateNavbarStyle(mViewGroup); } } private void UpdateNavbarStyle(View view){ if(mNavbarStyle < 0){ return; } ImageView tempview; switch(mNavbarStyle){ case NAV_BAR_STYLE_0: tempview = (ImageView) view.findViewById(R.id.ramos_hide_navbar_left); tempview.setVisibility(View.VISIBLE); tempview = (ImageView) view.findViewById(R.id.ramos_hide_navbar_right); tempview.setVisibility(View.VISIBLE); tempview = (ImageView) view.findViewById(R.id.back); tempview.setImageResource(R.drawable.ic_sysbar_back);//ic_sysbar_back_right tempview = (ImageView) view.findViewById(R.id.recent_apps); tempview.setImageResource(R.drawable.ic_sysbar_recent); break; case NAV_BAR_STYLE_1: tempview = (ImageView) view.findViewById(R.id.ramos_hide_navbar_left); tempview.setVisibility(View.VISIBLE); tempview = (ImageView) view.findViewById(R.id.ramos_hide_navbar_right); tempview.setVisibility(View.VISIBLE); tempview = (ImageView) view.findViewById(R.id.back); tempview.setImageResource(R.drawable.ic_sysbar_recent); // ic_sysbar_back ic_sysbar_recent tempview = (ImageView) view.findViewById(R.id.recent_apps); tempview.setImageResource(R.drawable.ic_sysbar_back_right); //ic_sysbar_back_right ic_sysbar_recent break; case NAV_BAR_STYLE_2: tempview = (ImageView) view.findViewById(R.id.ramos_hide_navbar_left); tempview.setVisibility(View.INVISIBLE); tempview = (ImageView) view.findViewById(R.id.ramos_hide_navbar_right); tempview.setVisibility(View.VISIBLE); tempview = (ImageView) view.findViewById(R.id.back); tempview.setImageResource(R.drawable.ic_sysbar_back);//ic_sysbar_back_right tempview = (ImageView) view.findViewById(R.id.recent_apps); tempview.setImageResource(R.drawable.ic_sysbar_recent); break; case NAV_BAR_STYLE_3: tempview = (ImageView) view.findViewById(R.id.ramos_hide_navbar_left); tempview.setVisibility(View.VISIBLE); tempview = (ImageView) view.findViewById(R.id.ramos_hide_navbar_right); tempview.setVisibility(View.INVISIBLE); tempview = (ImageView) view.findViewById(R.id.back); tempview.setImageResource(R.drawable.ic_sysbar_back);//ic_sysbar_back_right tempview = (ImageView) view.findViewById(R.id.recent_apps); tempview.setImageResource(R.drawable.ic_sysbar_recent); break; case NAV_BAR_STYLE_4: tempview = (ImageView) view.findViewById(R.id.ramos_hide_navbar_left); tempview.setVisibility(View.INVISIBLE); tempview = (ImageView) view.findViewById(R.id.ramos_hide_navbar_right); tempview.setVisibility(View.VISIBLE); tempview = (ImageView) view.findViewById(R.id.back); tempview.setImageResource(R.drawable.ic_sysbar_recent); // ic_sysbar_back ic_sysbar_recent tempview = (ImageView) view.findViewById(R.id.recent_apps); tempview.setImageResource(R.drawable.ic_sysbar_back_right); //ic_sysbar_back_right ic_sysbar_recent break; case NAV_BAR_STYLE_5: tempview = (ImageView) view.findViewById(R.id.ramos_hide_navbar_left); tempview.setVisibility(View.VISIBLE); tempview = (ImageView) view.findViewById(R.id.ramos_hide_navbar_right); tempview.setVisibility(View.INVISIBLE); tempview = (ImageView) view.findViewById(R.id.back); tempview.setImageResource(R.drawable.ic_sysbar_recent); // ic_sysbar_back ic_sysbar_recent tempview = (ImageView) view.findViewById(R.id.recent_apps); tempview.setImageResource(R.drawable.ic_sysbar_back_right); //ic_sysbar_back_right ic_sysbar_recent break; case NAV_BAR_STYLE_6: tempview = (ImageView) view.findViewById(R.id.ramos_hide_navbar_left); tempview.setVisibility(View.INVISIBLE); tempview = (ImageView) view.findViewById(R.id.ramos_hide_navbar_right); tempview.setVisibility(View.INVISIBLE); tempview = (ImageView) view.findViewById(R.id.back); tempview.setImageResource(R.drawable.ic_sysbar_back);//ic_sysbar_back_right tempview = (ImageView) view.findViewById(R.id.recent_apps); tempview.setImageResource(R.drawable.ic_sysbar_recent); break; case NAV_BAR_STYLE_7: tempview = (ImageView) view.findViewById(R.id.ramos_hide_navbar_left); tempview.setVisibility(View.INVISIBLE); tempview = (ImageView) view.findViewById(R.id.ramos_hide_navbar_right); tempview.setVisibility(View.INVISIBLE); tempview = (ImageView) view.findViewById(R.id.back); tempview.setImageResource(R.drawable.ic_sysbar_recent); // ic_sysbar_back ic_sysbar_recent tempview = (ImageView) view.findViewById(R.id.recent_apps); tempview.setImageResource(R.drawable.ic_sysbar_back_right); //ic_sysbar_back_right ic_sysbar_recent break; default: break; } }
Settings.System.putInt(mActivity.getContentResolver(), Settings.System.RAMOS_NAVBAR_STYLE, style);
(3)最後在NavigationBarView裡面來實現其配置的虛擬按鈕(NavigationBar):
在NavigationBarView中通過ContentObserver來監控RAMOS_NAVBAR_STYLE值得變化,一旦變化,就會從新排序NavigationBarView中各個按鈕的顯示。
這裡,我是通過替換他們View的ID、ImageResourc和KeyCode以及他們的長按短按事件ClickLisener。
到這裡就算完結了,不過有兩個問題需要解決:
其一:在輸入法的彈出框出現的時候,來回隱藏和顯示虛擬按鈕(NavigationBar),會看到輸入框下面有黑色背景或者顯示不全等問題,如何解決呢?
其實這是因為輸入法彈出框比較獨立,沒有能夠試試刷新和布局造成的。解決辦法是:在包com.android.internal.policy.impl的PhoneWindow類有一個方法:
private void updateNavigationGuard(WindowInsets insets)
在這個方法裡面只需要做到每次虛擬按鈕(NavigationBar)有變化時候調用該方法即可:requestFitSystemWindows();
其二:每次要顯示虛擬按鈕(NavigationBar)時候,從底部往上滑動時候會觸發屏幕中其他view的click或者move觸摸事件,造成誤點擊了某個圖標,滑動了一些菜單等問題,如何破解?
首先在PhoneWindow中攔截不需要的觸摸操作,在PhoneWindow的DecorView中的onInterceptTouchEvent裡面把不需要的觸摸事件return true即可。
注意:DecorView是所有activity的顯示view的父view.
這裡難點就是如何判斷一個觸摸事件是不需要的,也就是說如何判斷一個觸摸事件是要顯示虛擬按鈕(NavigationBar)的 ,如果一個使用的操作(手勢)是來顯示虛擬按鈕(NavigationBar)的,那麼這個操作就不要用來做其他的,就可以把這次觸摸操作當成不需要的操作了。因為通常情況下,你不可能又要顯示虛擬按鈕(NavigationBar),又要點擊一個其他界面的按鈕。
如何判斷一個觸摸(手勢)是來顯示虛擬按鈕(NavigationBar)的呢? 這裡需要通過上面說到的PhoneWindowManager和SystemGesturesPointerEventListener了。
大致的辦法是:
在SystemGesturesPointerEventListener識別顯示虛擬按鈕(NavigationBar)的手勢,從底部滑動、從左、右邊滑動,這裡有一個要求,就是要盡快的識別出來,希望能在滑動的前3個MotionEvent事件識別出來,原來的從頂部往下滑動的手勢識別需要6個MotionEvent事件以上,這是不夠的。
然後在PhoneWindowManager定義一個正在開始顯示虛擬按鈕(NavigationBar)的boolean startToShowNavbar變量,並定義一個public方法來判斷startToShowNavbar的狀態。
private static boolean startToShowNavbar = false; //private static final int KEY_CODE_RAMOS_HIDE_NAVBAR = 1994; @Override public boolean hasShowingNavbar() { //Log.v("NavigationGuard", "RAMOS hasShowingNavbar startToShowNavbar="+startToShowNavbar); return startToShowNavbar; }如何復位startToShowNavbar這個變量呢,就是說如何判斷顯示虛擬按鈕(NavigationBar)已經完成呢?在PhoneWindowManager的方法layoutWindowLw被調用,並且在layoutWindowLw中出現了TYPE_NAVIGATION_BAR,就復位。如下修改的截圖:
最後在PhoneWindow的DecorView中的onInterceptTouchEvent判斷即可了,如下源碼:
private boolean getShowIngNavBar() { try { return WindowManagerHolder.sWindowManager.hasShowingNavbar(); } catch (RemoteException ex) { Log.e(TAG, "RAMOS getShowIngNavBar:", ex); return false; } }
完畢!
Android Fragment 動態創建Fragment是activity的界面中的一部分或一種行為。可以把多個Fragment組合到一個activity中來創建一個多
相對與視圖動畫 ,屬性動畫(android3.0提出的) 使用條件:完全彌補了View anim System的缺陷,你可以為一個對象的任何屬性添加動畫,(View或者非
收藏的Android非常好用的組件或者框架。 android框架 先說兩個網站: http://www.androidviews.net/ 很好的國外開源代碼站,就是訪
前言項目中有使用到水印效果,如下圖所示。在實現過程中,最終選用ItemDecoration來實現,其中有兩大步驟:自定義Drawable來完成水印圖片、使用ItemDec