Android側滑菜單完整詳細樣本(改進版)

來源:互聯網
上載者:User

Android側滑菜單完整詳細樣本(改進版)
MainActivity如下:

package cc.cd;import android.os.Bundle;import android.view.View;import android.view.View.OnClickListener;import android.widget.AdapterView;import android.widget.AdapterView.OnItemClickListener;import android.widget.ArrayAdapter;import android.widget.Button;import android.widget.ListView;import android.widget.Toast;import android.app.Activity;import android.content.Context;/** * Demo描述: * 將Patience5中使用的側滑菜單封裝為一個自訂控制項從而便於複用 *  * 該樣本在Patience5的不同之處: * 1 Patience5中採用的是LinearLayout布局,而該自訂控制項繼承自RelativeLayout * 2 在Patience5中是不斷改變menuView的leftMargin而在此處是不斷改變contentView的 *   rightMargin.這一點在閱讀代碼時要注意. *    * 參考資料: * http://blog.csdn.net/guolin_blog/article/details/8744400 * Thank you very much */public class MainActivity extends Activity {private Context mContext;private ListView mContentListView;    private Button mContentMenuButton;    private SlidingMenuRelativeLayout mSlidingMenuRelativeLayout;    private String [] listViewItems=new String [20];    private ArrayAdapter mArrayAdapter;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.main);init();}private void init(){mContext=this;mContentListView=(ListView) findViewById(R.id.contentListView);mContentMenuButton=(Button) findViewById(R.id.contentMenuButton);mContentMenuButton.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View view) {boolean isMenuVisible=mSlidingMenuRelativeLayout.isMenuVisible();if (isMenuVisible) {mSlidingMenuRelativeLayout.scrollToContent();} else {mSlidingMenuRelativeLayout.scrollToMenu();}}});mSlidingMenuRelativeLayout=(SlidingMenuRelativeLayout) findViewById(R.id.slidingMenuRelativeLayout);//滑動事件綁定在contentListView上mSlidingMenuRelativeLayout.setBindView(mContentListView);initListViewData();mArrayAdapter=new ArrayAdapter(mContext, android.R.layout.simple_list_item_1, listViewItems);mContentListView.setAdapter(mArrayAdapter);mContentListView.setOnItemClickListener(new OnItemClickListener() {@Overridepublic void onItemClick(AdapterView arg0, View arg1, int position, long arg3) {String item = listViewItems[position];Toast.makeText(mContext, item, Toast.LENGTH_SHORT).show();}});}private void initListViewData(){String temp=null;for (int i = 0; i < 20; i++) {temp="This is "+i;listViewItems[i]=temp;}}}


SlidingMenuRelativeLayout如下:

package cc.cd;import android.content.Context;import android.os.AsyncTask;import android.util.AttributeSet;import android.view.MotionEvent;import android.view.VelocityTracker;import android.view.View;import android.view.ViewConfiguration;import android.view.WindowManager;import android.widget.Button;import android.widget.RelativeLayout;import android.widget.Toast;/** * 樣本說明: * 1 該自訂控制項繼承自RelativeLayout * 2 在布局檔案中有兩部分menuView和contentView.因為是RelativeLayout布局 *   所以這兩者是重疊的. * 3 通過不斷改變contentView的rightMargin值來顯示和隱藏menuView * 4 請注意在xml布局檔案中設定了contentView對齊為layout_alignParentRight *   設定了menuView對齊為 android:layout_alignParentLeft. *   所以注意代碼: *   //設定contentView的最小和最大rightMargin值 *   contentParamsMinRightMargin=-mMenuLayoutParams.width; *   contentParamsMaxRightMargin=0; *   這個還是挺巧妙的.因為contentView是相對於父控制項靠右對齊的.所以在原始狀態下 *   他的rightMargin的值為0.當手指按在contentView上滑向螢幕的右側時可以不斷減小 *   它的rightMargin,從而導致contentView移向螢幕的右側本來被其遮蓋的menuView *   也就隨之顯現. *   所以contentView的最大rightMargin值=0,這個也就是我們進入App後看到的: *   顯示了contentView,遮蓋了menuView.它的rightMargin=0;與父控制項右側對齊. *   當contentView移向螢幕的右邊時,它的rightMargin在逐漸減小直到rightMargin *   的絕對值對於menuView的寬度. *    *   *  代碼細節: *  1 mMenuLayoutParams和mContentLayoutParams都是 *    MarginLayoutParams類型的,因為menuView和conentView的父控制項 *    是自訂的SlidingMenuRelativeLayout,而不是常用的系統XXXLayout *  2 在手指在ListView上滑動然後抬起,有時會出現ListView的item被按下且一直沒有 *    彈起的情況.造成該現象的原因暫時不明,但可用unFocusBindView()方法使得 *    ListView失去焦點.該方法在樣本中兩次被調用.可以將其注釋後觀察效果. *    關於這點,在代碼中仍然存在小bug.需以後繼續最佳化. */public class SlidingMenuRelativeLayout extends RelativeLayout {private int screenWidth;private View contentView;private View menuView;private View mBindView;private float xDown;private float xMove;private float xUp;private float yDown;private float yMove;private float yUp;private Button mButton;private Context mContext;// 當前是否在滑動private boolean isSliding;// 在被判定為滾動之前使用者手指可移動的最大值private int scaledTouchSlop;// menu是否可見的標誌位,該值在滑動過程中無效.// 只有在滑動結束後,完全顯示或隱藏menu時才會更改此值private boolean isMenuVisible = false;private int contentParamsMaxRightMargin = 0;private int contentParamsMinRightMargin = 0;// 速度追蹤private VelocityTracker mVelocityTracker;// 閾值public static final int VELOCITY_THRESHOLD = 200;// TAGprivate final static String TAG = "SlidingMenuRelativeLayout";// menu的布局LayoutParamsprivate MarginLayoutParams mMenuLayoutParams;// content的布局LayoutParamsprivate MarginLayoutParams mContentLayoutParams;public SlidingMenuRelativeLayout(Context context) {super(context);}public SlidingMenuRelativeLayout(Context context, AttributeSet attrs) {super(context, attrs);init(context);}public SlidingMenuRelativeLayout(Context context, AttributeSet attrs,int defStyle) {super(context, attrs, defStyle);}@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {super.onLayout(changed, l, t, r, b);if (changed) {// 初始化contentViewcontentView = getChildAt(1);mContentLayoutParams = (MarginLayoutParams) contentView.getLayoutParams();// 將contentView的寬度設定為螢幕的寬度mContentLayoutParams.width = screenWidth;contentView.setLayoutParams(mContentLayoutParams);// 初始化menuViewmenuView = getChildAt(0);mMenuLayoutParams = (MarginLayoutParams) menuView.getLayoutParams();// 設定contentView的最小和最大rightMargin值.contentParamsMinRightMargin = -mMenuLayoutParams.width;contentParamsMaxRightMargin = 0;mButton = (Button) menuView.findViewById(R.id.menuButton);mButton.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View view) {Toast.makeText(mContext, "Hello", Toast.LENGTH_SHORT).show();}});}}private void init(Context context) {mContext = context;// 擷取螢幕寬度WindowManager windowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);screenWidth = windowManager.getDefaultDisplay().getWidth();// 擷取ScaledTouchSlopscaledTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();}/** * 註冊處理Touch事件的View 在改樣本中處理了ListView的Touch來顯示和隱藏menuView的. * 在實際開發中可以依據需求設定其他為其他控制項 */public void setBindView(View bindView) {mBindView = bindView;mBindView.setOnTouchListener(new TouchListenerImpl());}// 使BindView失去焦點public void unFocusBindView() {if (mBindView != null) {mBindView.setPressed(false);mBindView.setFocusable(false);mBindView.setFocusableInTouchMode(false);}}private class TouchListenerImpl implements OnTouchListener {@Overridepublic boolean onTouch(View v, MotionEvent event) {// 開始速度追蹤startVelocityTracker(event);switch (event.getAction()) {case MotionEvent.ACTION_DOWN:xDown = event.getRawX();yDown = event.getRawY();break;case MotionEvent.ACTION_MOVE:xMove = event.getRawX();yMove = event.getRawY();int distanceX = (int) (xMove - xDown);int distanceY = (int) (yMove - yDown);// 手指滑向螢幕右側,distanceX為正數if (!isMenuVisible&& distanceX >= scaledTouchSlop&& (isSliding || Math.abs(distanceY) <= scaledTouchSlop)) {isSliding = true;mContentLayoutParams.rightMargin = -distanceX;// 處理越界的情況if (mContentLayoutParams.rightMargin < contentParamsMinRightMargin) {mContentLayoutParams.rightMargin = contentParamsMinRightMargin;}// 設定contentView的LayoutParamscontentView.setLayoutParams(mContentLayoutParams);}// 手指滑向螢幕左側,distanceX為負數if (isMenuVisible && -distanceX >= scaledTouchSlop) {isSliding = true;mContentLayoutParams.rightMargin = contentParamsMinRightMargin - distanceX;// 處理越界的情況if (mContentLayoutParams.rightMargin > contentParamsMaxRightMargin) {mContentLayoutParams.rightMargin = contentParamsMaxRightMargin;}// 設定contentView的LayoutParamscontentView.setLayoutParams(mContentLayoutParams);}break;case MotionEvent.ACTION_UP:xUp = event.getRawX();int upDistanceX = (int) (xUp - xDown);if (isSliding) {// 判斷手勢意圖想顯示menuif (wantToShowMenu()) {// 判斷是否顯示menuif (shouldScrollToMenu()) {scrollToMenu();} else {scrollToContent();}}// 判斷手勢意圖想顯示contentif (wantToShowContent()) {// 判斷是否顯示contentif (shouldScrollToContent()) {scrollToContent();} else {scrollToMenu();}}} else {// 當完全顯示菜單介面時,點擊僅能看到的contentView可將其完全顯示if (upDistanceX < scaledTouchSlop && isMenuVisible) {scrollToContent();}}// 終止速度追蹤stopVelocityTracker();break;default:break;}/** * 在處理完DOWN,MOVE,UP後進行該if...else判斷.  * 1 如果不是在滑動狀態,則返回false.否則ListView無法滑動且其Item無法點擊.  * 2 若在滑動,則讓ListView失去焦點. */if (!isSliding) {return false;} else {unFocusBindView();}return true;}}/** * 判斷當前手勢是否想顯示菜單Menu  * 判斷條件:  * 1 抬起座標大於按下座標  * 2 menu本身不可見 */private boolean wantToShowMenu() {return ((xUp - xDown > 0) && (!isMenuVisible));}/** * 判斷是否應該將menu完整顯示出來  * 判斷條件: 滑動距離大於菜單的二分之一  *        或者滑動速度大於速度閾值VELOCITY_THRESHOLD */private boolean shouldScrollToMenu() {return ((xUp - xDown > mMenuLayoutParams.width / 2) || (getScrollVelocity() > VELOCITY_THRESHOLD));}/** * 將螢幕滾動到menu.即將menu完整顯示. */public void scrollToMenu() {new ScrollAsyncTask().execute(-30);}/** * 判斷當前手勢是否想顯示菜單Content  * 判斷條件:  * 1 抬起座標小於按下座標 * 2 menu本身可見 */private boolean wantToShowContent() {return ((xUp - xDown < 0) && (isMenuVisible));}/** * 判斷是否應該將content完整顯示出來  * 判斷條件: 滑動距離大於菜單的二分之一  *        或者滑動速度大於速度閾值VELOCITY_THRESHOLD */private boolean shouldScrollToContent() {return ((xDown - xUp > mMenuLayoutParams.width / 2) || (getScrollVelocity() > VELOCITY_THRESHOLD));}/** * 將螢幕滾動到content.即將content完整顯示 */public void scrollToContent() {new ScrollAsyncTask().execute(30);}public boolean isMenuVisible() {return isMenuVisible;}/** * 開始速度追蹤 */private void startVelocityTracker(MotionEvent event) {if (mVelocityTracker == null) {mVelocityTracker = VelocityTracker.obtain();}mVelocityTracker.addMovement(event);}/** * 擷取在content上X方向的手指滑動速度 */private int getScrollVelocity() {// 設定VelocityTracker單位.1000表示1秒時間內運動的像素mVelocityTracker.computeCurrentVelocity(1000);// 擷取在1秒內X方向所滑動像素值int xVelocity = (int) mVelocityTracker.getXVelocity();return Math.abs(xVelocity);}/** * 終止速度追蹤 */private void stopVelocityTracker() {if (mVelocityTracker != null) {mVelocityTracker.recycle();mVelocityTracker = null;}}/** * 利用非同步任務不斷修改contentView的LayoutParams中的rightMargin從而達到 contentView視圖移動的效果 */private class ScrollAsyncTask extends AsyncTask {@Overrideprotected Integer doInBackground(Integer... speed) {int contentLayoutParamsRightMargin = mContentLayoutParams.rightMargin;while (true) {// 每次變化的speedcontentLayoutParamsRightMargin = contentLayoutParamsRightMargin + speed[0];// 若越界,則處理越界且跳出迴圈if (contentLayoutParamsRightMargin > contentParamsMaxRightMargin) {contentLayoutParamsRightMargin = contentParamsMaxRightMargin;break;}// 若越界,則處理越界且跳出迴圈if (contentLayoutParamsRightMargin < contentParamsMinRightMargin) {contentLayoutParamsRightMargin = contentParamsMinRightMargin;break;}// 通知進度更新publishProgress(contentLayoutParamsRightMargin);// 線程睡眠15毫秒,便於體現滾動效果try {Thread.sleep(15);} catch (Exception e) {}}// 依據滑動的速度設定標誌位isMenuVisibleif (speed[0] > 0) {isMenuVisible = false;} else {isMenuVisible = true;}isSliding = false;return contentLayoutParamsRightMargin;}@Overrideprotected void onProgressUpdate(Integer... rightMargin) {super.onProgressUpdate(rightMargin);mContentLayoutParams.rightMargin = rightMargin[0];contentView.setLayoutParams(mContentLayoutParams);unFocusBindView();}@Overrideprotected void onPostExecute(Integer rightMargin) {super.onPostExecute(rightMargin);mContentLayoutParams.rightMargin = rightMargin;contentView.setLayoutParams(mContentLayoutParams);}}}

main.xml如下:

                                                


聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.