效果图
思考
可以看出滑动的是两个layout,所以自定义的侧滑控件应该继承ViewGroup,实现onMessure()和onLayout()方法,为了简化操作,可以继承android系统已经实现好的ViewGroup的子类 —— FrameLayout,这样就不用自己去测量了。
onLayout()方法中,初始化的时候,要将侧边菜单的布局放到屏幕左边看不到的地方。menuView.layout(-menuWidth,0,0,menuView.getMeasuredHeight()); 将主页面的布局完全显示。mainView.layout(0, 0, r, b);
为了实现偏移控制,重写一下computeScroll()方法,在初始化自定义的侧滑菜单时创建一个Scroller()对象。关于Scroller类和computeScroll()方法可参考这个链接:http://www.cnblogs.com/wanqieddy/archive/2012/05/05/2484534.html
接下来就是在onTouchEvent()方法中进行判断了。
手指按下时,记录按下的x坐标。
手指移动时,计算出在x方向滑动的距离deltaX,获取当前view的左边在屏幕上的x的距离getScrollX(),减去deltaX,就得到应该移动的距离了。
手指抬起时,判断一下,如果偏移的距离大于菜单布局的宽的一半,就关闭菜单,否则,打开菜单。
步骤
1. 主页面的布局
<RelativeLayout 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"> <com.example.slidingmenu.view.SlidingMenu android:id="@+id/slidingMenu" android:layout_width="match_parent" android:layout_height="match_parent"> <include layout="@layout/layout_menu" /> <include layout="@layout/layout_main" /> </com.example.slidingmenu.view.SlidingMenu> </RelativeLayout>
2. 主内容的布局
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#55666666" android:orientation="vertical"> <LinearLayout android:layout_width="match_parent" android:layout_height="60dp" android:background="@mipmap/top_bar_bg" android:gravity="center_vertical"> <ImageView android:id="@+id/btn_back" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@mipmap/main_back" /> <View android:layout_width="1dp" android:layout_height="match_parent" android:layout_marginBottom="5dp" android:layout_marginTop="5dp" android:background="@mipmap/top_bar_divider" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="15dp" android:text="网易新闻" android:textColor="#ffffff" android:textSize="22sp" /> </LinearLayout> <TextView android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:text="头疼的番茄..." android:textColor="#000000" android:textSize="30sp" /> </LinearLayout>
3. 侧边菜单栏的布局
<?xml version="1.0" encoding="utf-8"?> <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="240dp" android:layout_height="match_parent" android:background="@mipmap/menu_bg" android:orientation="vertical"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:paddingBottom="300dp"> <TextView style="@style/MenuTabText" android:background="#33aa9900" android:drawableLeft="@mipmap/tab_news" android:text="新闻" /> <TextView style="@style/MenuTabText" android:drawableLeft="@mipmap/tab_read" android:text="订阅" /> <TextView style="@style/MenuTabText" android:drawableLeft="@mipmap/tab_ties" android:text="跟帖" /> <TextView style="@style/MenuTabText" android:drawableLeft="@mipmap/tab_pics" android:text="图片" /> <TextView style="@style/MenuTabText" android:drawableLeft="@mipmap/tab_ugc" android:text="话题" /> <TextView style="@style/MenuTabText" android:drawableLeft="@mipmap/tab_vote" android:text="投票" /> <TextView style="@style/MenuTabText" android:drawableLeft="@mipmap/tab_focus" android:text="聚合阅读" /> </LinearLayout> </ScrollView>
4. 自定义菜单的代码
public class SlidingMenu extends FrameLayout{ private View menuView,mainView; private int menuWidth = 0; private Scroller scroller; public SlidingMenu(Context context, AttributeSet attrs) { super(context, attrs); init(); } public SlidingMenu(Context context) { super(context); init(); } private void init(){ scroller = new Scroller(getContext()); } /** * 当1级的子view全部加载完调用,可以用初始化子view的引用 * 注意,这里无法获取子view的宽高 */ @Override protected void onFinishInflate() { super.onFinishInflate(); menuView = getChildAt(0); mainView = getChildAt(1); menuWidth = menuView.getLayoutParams().width; } /** * widthMeasureSpec和heightMeasureSpec是系统测量SlideMenu时传入的参数, * 这2个参数测量出的宽高能让SlideMenu充满窗体,其实是正好等于屏幕宽高 */ // @Override // protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // super.onMeasure(widthMeasureSpec, heightMeasureSpec); // // int measureSpec = MeasureSpec.makeMeasureSpec(menuWidth, MeasureSpec.EXACTLY); // // //测量所有子view的宽高 // //通过getLayoutParams方法可以获取到布局文件中指定宽高 // menuView.measure(measureSpec, heightMeasureSpec); // //直接使用SlideMenu的测量参数,因为它的宽高都是充满父窗体 // mainView.measure(widthMeasureSpec, heightMeasureSpec); // // } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: downX = (int) ev.getX(); break; case MotionEvent.ACTION_MOVE: int deltaX = (int) ( ev.getX()- downX); if(Math.abs(deltaX)>8){ return true; } break; } return super.onInterceptTouchEvent(ev); // return super.onInterceptTouchEvent(ev); } /** * l: 当前子view的左边在父view的坐标系中的x坐标 * t: 当前子view的顶边在父view的坐标系中的y坐标 */ @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { // Log.e("MAIN", "L: "+l+" t: "+t +" r: "+r + " b: "+b); menuView.layout(-menuWidth, 0, 0, menuView.getMeasuredHeight()); mainView.layout(0, 0, r, b); } private int downX; @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: downX = (int) event.getX(); break; case MotionEvent.ACTION_MOVE: int moveX = (int) event.getX(); int deltaX = (int) ( moveX- downX); int newScrollX = getScrollX() - deltaX; if(newScrollX<-menuWidth)newScrollX = -menuWidth; if(newScrollX>0)newScrollX = 0; Log.e("Main", "scrollX: " + getScrollX()); scrollTo(newScrollX, 0); downX = moveX; break; case MotionEvent.ACTION_UP: //1.使用自定义动画 // ScrollAnimation scrollAnimation; // if(getScrollX()>-menuWidth/2){ // //关闭菜单 //// scrollTo(0, 0); // scrollAnimation = new ScrollAnimation(this, 0); // }else { // //打开菜单 //// scrollTo(-menuWidth, 0); // scrollAnimation = new ScrollAnimation(this, -menuWidth); // } // startAnimation(scrollAnimation); //2.使用Scroller if(getScrollX()>-menuWidth/2){ // //关闭菜单 closeMenu(); }else { //打开菜单 openMenu(); } break; } return true; } private void closeMenu(){ scroller.startScroll(getScrollX(), 0, 0-getScrollX(), 0, 400); invalidate(); } private void openMenu(){ scroller.startScroll(getScrollX(), 0, -menuWidth-getScrollX(), 0, 400); invalidate(); } /** * Scroller不主动去调用这个方法 * 而invalidate()可以掉这个方法 * invalidate->draw->computeScroll */ @Override public void computeScroll() { super.computeScroll(); if(scroller.computeScrollOffset()){//返回true,表示动画没结束 scrollTo(scroller.getCurrX(), 0); invalidate(); } } /** * 切换菜单的开和关 */ public void switchMenu() { if(getScrollX()==0){ //需要打开 openMenu(); }else { //需要关闭 closeMenu(); } } }
5. 主函数的代码
public class MainActivity extends Activity { private ImageView btn_back; private SlidingMenu slideMenu; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.activity_main); btn_back = (ImageView) findViewById(R.id.btn_back); slideMenu = (SlidingMenu) findViewById(R.id.slidingMenu); btn_back.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { slideMenu.switchMenu(); } }); } }
6. 补充 :第一种滑动方式 自定义动画的代码
public class ScrollAnimation extends Animation{ private View view; private int targetScrollX; private int startScrollX; private int totalValue; public ScrollAnimation(View view, int targetScrollX) { super(); this.view = view; this.targetScrollX = targetScrollX; startScrollX = view.getScrollX(); totalValue = this.targetScrollX - startScrollX; int time = Math.abs(totalValue); setDuration(time); } /** * 在指定的时间内一直执行该方法,直到动画结束 * interpolatedTime:0-1 标识动画执行的进度或者百分比 * time : 0 - 0.5 - 0.7 - 1 * value: 10 - 60 - 80 - 110 * 当前的值 = 起始值 + 总的差值*interpolatedTime */ @Override protected void applyTransformation(float interpolatedTime, Transformation t) { super.applyTransformation(interpolatedTime, t); int currentScrollX = (int) (startScrollX + totalValue*interpolatedTime); view.scrollTo(currentScrollX, 0); } }