MainActivity如下:
main.xml如下:
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<String> mArrayAdapter; @Override protected 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() { @Override public 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<String>(mContext, android.R.layout.simple_list_item_1, listViewItems); mContentListView.setAdapter(mArrayAdapter); mContentListView.setOnItemClickListener(new OnItemClickListener() { @Override public 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; // TAG private final static String TAG = "SlidingMenuRelativeLayout"; // menu的布局LayoutParams private MarginLayoutParams mMenuLayoutParams; // content的布局LayoutParams private 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); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); if (changed) { // 初始化contentView contentView = getChildAt(1); mContentLayoutParams = (MarginLayoutParams) contentView.getLayoutParams(); // 将contentView的宽度设置为屏幕的宽度 mContentLayoutParams.width = screenWidth; contentView.setLayoutParams(mContentLayoutParams); // 初始化menuView menuView = getChildAt(0); mMenuLayoutParams = (MarginLayoutParams) menuView.getLayoutParams(); // 设置contentView的最小和最大rightMargin值. contentParamsMinRightMargin = -mMenuLayoutParams.width; contentParamsMaxRightMargin = 0; mButton = (Button) menuView.findViewById(R.id.menuButton); mButton.setOnClickListener(new OnClickListener() { @Override public 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(); // 获取ScaledTouchSlop scaledTouchSlop = 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 { @Override public 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的LayoutParams contentView.setLayoutParams(mContentLayoutParams); } // 手指滑向屏幕左侧,distanceX为负数 if (isMenuVisible && -distanceX >= scaledTouchSlop) { isSliding = true; mContentLayoutParams.rightMargin = contentParamsMinRightMargin - distanceX; // 处理越界的情况 if (mContentLayoutParams.rightMargin > contentParamsMaxRightMargin) { mContentLayoutParams.rightMargin = contentParamsMaxRightMargin; } // 设置contentView的LayoutParams contentView.setLayoutParams(mContentLayoutParams); } break; case MotionEvent.ACTION_UP: xUp = event.getRawX(); int upDistanceX = (int) (xUp - xDown); if (isSliding) { // 判断手势意图想显示menu if (wantToShowMenu()) { // 判断是否显示menu if (shouldScrollToMenu()) { scrollToMenu(); } else { scrollToContent(); } } // 判断手势意图想显示content if (wantToShowContent()) { // 判断是否显示content if (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<Integer, Integer, Integer> { @Override protected Integer doInBackground(Integer... speed) { int contentLayoutParamsRightMargin = mContentLayoutParams.rightMargin; while (true) { // 每次变化的speed contentLayoutParamsRightMargin = 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) { } } // 依据滑动的速度设置标志位isMenuVisible if (speed[0] > 0) { isMenuVisible = false; } else { isMenuVisible = true; } isSliding = false; return contentLayoutParamsRightMargin; } @Override protected void onProgressUpdate(Integer... rightMargin) { super.onProgressUpdate(rightMargin); mContentLayoutParams.rightMargin = rightMargin[0]; contentView.setLayoutParams(mContentLayoutParams); unFocusBindView(); } @Override protected void onPostExecute(Integer rightMargin) { super.onPostExecute(rightMargin); mContentLayoutParams.rightMargin = rightMargin; contentView.setLayoutParams(mContentLayoutParams); } } }
main.xml如下:
<cc.cd.SlidingMenuRelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/slidingMenuRelativeLayout" android:layout_width="match_parent" android:layout_height="match_parent" > <RelativeLayout android:id="@+id/menuRelativeLayout" android:layout_width="270dip" android:layout_height="match_parent" android:layout_alignParentLeft="true" android:background="#00ccff"> <TextView android:id="@+id/menuTextView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:text="This is Menu" android:textColor="#000000" android:textSize="25sp" /> <Button android:id="@+id/menuButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:text="Click here" android:textColor="#000000" android:textSize="25sp" /> </RelativeLayout> <LinearLayout android:id="@+id/contentLinearLayout" android:layout_width="320dip" android:layout_height="match_parent" android:layout_alignParentRight="true" android:background="#e9e9e9" android:orientation="vertical" > <Button android:id="@+id/contentMenuButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Menu" /> <ListView android:id="@+id/contentListView" android:layout_width="fill_parent" android:layout_height="fill_parent" android:cacheColorHint="#00000000" /> </LinearLayout> </cc.cd.SlidingMenuRelativeLayout>