Android 自定义ViewGroup(侧滑菜单)

1,实现效果

2,实现逻辑

 【1】界面搭建,menu菜单  提取样式

  • main 布局 

<?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:orientation="vertical" >

    <LinearLayout

        android:layout_width="match_parent"

        android:layout_height="wrap_content"

        android:background="@drawable/top_bar_bg"

        android:orientation="horizontal" >

        <Button

            android:id="@+id/btn_back"

            android:layout_width="wrap_content"

            android:layout_height="wrap_content"

            android:background="@drawable/main_back" />

        <ImageView

            android:layout_width="wrap_content"

            android:layout_height="wrap_content"

            android:paddingTop="3dp"

            android:src="@drawable/top_bar_divider" />

        <TextView

            android:layout_width="match_parent"

            android:layout_height="wrap_content"

            android:layout_marginLeft="50dp"

            android:text="小帅xxx"

            android:textColor="#ffffff"

            android:textSize="25sp" />

    </LinearLayout>

      <TextView

            android:layout_width="match_parent"

            android:layout_height="match_parent"

            android:text="牛奶奶说牛奶是刘奶奶的奶奶····"

            android:gravity="center"

            android:textSize="25sp" />

</LinearLayout>
  • 抽取字体的样式 

<style name="MenuText" parent="android:Widget.TextView">

        <item name="android:layout_width">match_parent</item>

        <item name="android:layout_height">wrap_content</item>

        <item name="android:drawablePadding">15dp</item>

        <item name="android:gravity">center_vertical</item>

        <item name="android:padding">15dp</item>

        <item name="android:textColor">#fff</item>

        <item name="android:textSize">25sp</item>

        <item name="android:textStyle">bold</item>

    </style>
  •  menu 的布局 

<?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" >

    <LinearLayout

        android:layout_width="240dp"

        android:layout_height="match_parent"

        android:background="@drawable/menu_bg"

        android:orientation="vertical" >

        <TextView

            style="@style/MenuText"

            android:background="#571F2C"

            android:drawableLeft="@drawable/tab_news"

            android:text="新闻" />

        <TextView

            style="@style/MenuText"

            android:drawableLeft="@drawable/tab_read"

            android:text="订阅" />

        <TextView

            style="@style/MenuText"

            android:drawableLeft="@drawable/tab_ties"

            android:text="跟帖" />

        <TextView

            style="@style/MenuText"

            android:drawableLeft="@drawable/tab_pics"

            android:text="图片" />

        <TextView

            style="@style/MenuText"

            android:drawableLeft="@drawable/tab_ugc"

            android:text="话题" />

        <TextView

            style="@style/MenuText"

            android:drawableLeft="@drawable/tab_vote"

            android:text="投票" />

        <TextView

            style="@style/MenuText"

            android:drawableLeft="@drawable/tab_focus"

            android:text="聚合阅读" />

    </LinearLayout>

</ScrollView>

【2】定义一个类创建自定义控件

public class SlidingMenu extends RelativeLayout{



    public SlidingMenu(Context context, AttributeSet attrs) {

        super(context, attrs);

    }

    //在这个方法里面 自己对孩子进行排版 不使用系统默认的排版方式

    @Override

    protected void onLayout(boolean changed, int l, int t, int r, int b) {

    }
  • 布局中声明控件

<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"

    tools:context=".MainActivity" >

    <com.xiaoshuai.slidingmenuhm.SlidingMenu

        android:id="@+id/slidingMenu1"

        android:layout_width="match_parent"

        android:layout_height="match_parent">

        <!-- 添加自己的孩子  menu菜单和main主界面 -->

        <include layout="@layout/menu" />

        <include layout="@layout/main" />

    </com.xiaoshuai.slidingmenuhm.SlidingMenu>

</RelativeLayout>

MainActivity在加载布局中去掉标题栏 

  •  

        //去掉标题栏

        requestWindowFeature(Window.FEATURE_NO_TITLE);

        setContentView(R.layout.activity_main);

  • SlidingMenu的onlayout 自己进行排版

//在这个方法里面 自己对孩子进行排版 不使用系统默认的排版方式 

   @Override

    protected void onLayout(boolean changed, int l, int t, int r, int b) {

        

        //[1]找到menu和main孩子

        View menuView = getChildAt(0);

        View mainView = getChildAt(1);

        //[2]获取菜单的宽度

        menuWidth = menuView.getMeasuredWidth();

        //[3]对menu菜单进行排版

        menuView.layout(-menuWidth, t, l, b);

        //[4]对main界面排版

        mainView.layout(l, t, r, b);

        

    }

【3】移动效果:

  • 创建onTouchEvent获取对应的3种事件,按下和移动获取坐标。获取移动的偏移量

//处理当前view的事件

    @Override

    public boolean onTouchEvent(MotionEvent event) {

        switch (event.getAction()) {

        case MotionEvent.ACTION_DOWN:  //按下

            //[1]获取手指按下的坐标

            downX = event.getX();

            break;

        case MotionEvent.ACTION_MOVE: //移动

            //[2]算出移动的距离

            float moveX = event.getX();

            distaceX = (int) (moveX - downX)+currentMenuPosition;

            //[4]开始滚动view

            startScrollViewContent(distaceX);

            break;

重写scrollTo方法来实现移动

  •  

/**

     * 由于scrollTo 方法 系统在实现的时候 传入正值往左移动  传入负值往右移动 所以我重写这个方

     *  符合中国 人的思维

     * @param x

     */

    public void startScrollViewContent(int x){

        super.scrollTo(-x, 0);

    }

  • 对边界进行处理 menuWidth 是menu 宽度

  case MotionEvent.ACTION_MOVE: //移动

            //[2]算出移动的距离

            float moveX = event.getX();

            distaceX = (int) (moveX - downX)+currentMenuPosition;

            //[3]对边界进行处理

            if (distaceX <= 0) {

                distaceX = 0;

            }else if (distaceX >=menuWidth) {

                distaceX = menuWidth;

            }

            

            //[4]开始滚动view

            startScrollViewContent(distaceX);

            break;
  • 抬起时做距离判断效果 

    

    case MotionEvent.ACTION_UP:    //抬起

            //[5]当手指抬起后, 如果移动的距离 < 菜单的宽/2 就回到左边  否则像右移动

            if (distaceX < menuWidth/2) {

                startScrollViewContent(0);

            }else {

                startScrollViewContent(menuWidth); 

            }

            break;

        }

 

【4】实现平滑滚动

invalidate--drawchild ---child.draw --computeScroll()

  • 创建对象

    //创建Scroller的实例  可以收集数据 并且产生一个动画效果

        mScroller = new Scroller(getContext());

  • ACTION_UP方法中实现平滑滚动效果

  • currentMenuPosition 定义的一个菜单移动到哪里的变量
    
        case MotionEvent.ACTION_UP:    //抬起
    
                //[5]当手指抬起后, 如果移动的距离 < 菜单的宽/2 就回到左边  否则像右移动
    
                if (distaceX < menuWidth/2) {
    
                    currentMenuPosition = 0;
    
                }else {
    
                    currentMenuPosition = menuWidth;
    
                }
    
                
    
                //[6]当一段逻辑同时用到多次 我们最好做抽取
    
                
    
                int startX = distaceX; //就是移动的距离
    
                int endX = currentMenuPosition;
    
                int dx = endX - startX;
    
                int duration = Math.abs(dx)*20;
    
                //产生模拟的数据
    
                mScroller.startScroll(startX, 0, dx, 0, duration);
    
                //调用invalidata 请求绘制
    
                invalidate();  //---->drawchild ---child.draw --computeScroll()
    
                
    
                break;
  • computeScroll 方法中获取模拟的移动数据进行滑动

  

  //取出我们模拟滚动的数据

    @Override

    public void computeScroll() {

        if (mScroller.computeScrollOffset()) {

            //[1]取出我们模拟的数据

             int currX = mScroller.getCurrX();

            //[2]调用startScrollViewContent 让view的内容滚动

            startScrollViewContent(currX);

            invalidate();

         }

    }

【5】在menu布局中无法左右滑动,处理孩子的滑动事件

  • onInterceptTouchEvent判断孩子滑动的距离

 

   //当用户横着滑动的距离 > 竖着滑动的距离 就拦截事件 不让孩子处理事件 否则就让孩子处理事件

    @Override

    public boolean onInterceptTouchEvent(MotionEvent ev) {

        switch (ev.getAction()) {

        case MotionEvent.ACTION_DOWN:

             downX = ev.getX();

             downY = ev.getY();

            break;

        case MotionEvent.ACTION_MOVE:

            float moveX = ev.getX();

            float moveY = ev.getY();

            //[1]算出X轴移动的距离 和 Y移动的距离

            int distanceX = (int) (moveX - downX);

            int distanceY = (int) (moveY - downY);

            //[2]如果x轴移动的距离大于Y轴移动的距离 就拦截事件

            if (Math.abs(distanceX) > Math.abs(distanceY)) {

                return true;

            }

            break;

        case MotionEvent.ACTION_UP:

            break;

        }

        return super.onInterceptTouchEvent(ev);

    }

【6】MainActivity 中点击事件实现平滑滚动

  • 设置点击事件

//[1]找到slidingmenu

        sm = (SlidingMenu) findViewById(R.id.slidingMenu1);

        //[2]给主页面的按钮设置点击事件 当菜单是关闭状态 点击按钮菜单打开  如果是打开状态就关闭

        findViewById(R.id.btn_back).setOnClickListener(new OnClickListener() {

            

            @Override

            public void onClick(View v) {

                

                sm.setOnMenuIsOpen();

            }

        });

创建setOnMenuIsOpen 方法,判断菜单的位置来确认开或者关

  •  
    //通过这个方法控制菜单打开或者关闭

    public void setOnMenuIsOpen() {

        int startX = 0;

        if (currentMenuPosition == 0) {

            //说明菜单是关闭状态  需要打开

            currentMenuPosition = menuWidth;

        }else if (currentMenuPosition == menuWidth) {

            //说明菜单是打开状态  需要关闭

            currentMenuPosition = 0;

            startX = menuWidth;

        }

        //调用平滑滚动的方法

        startScroller(startX, currentMenuPosition);

    }

3,实现的全部代码

package com.xiaoshuai.www.slidingmenu;

import android.content.Context;

import android.util.AttributeSet;

import android.view.MotionEvent;

import android.view.View;

import android.widget.RelativeLayout;

import android.widget.Scroller;

public class SlidingMenu extends RelativeLayout{





    private float downX;

    /**代表菜单的宽度**/

    private int menuWidth;

    private int distaceX;

    private Scroller mScroller;

    /**代表当前菜单的位置**/

    private int currentMenuPosition;

    private float downY;





    public SlidingMenu(Context context, AttributeSet attrs) {

        super(context, attrs);

        //创建Scroller的实例  可以收集数据 并且产生一个动画效果

        mScroller = new Scroller(getContext());





    }



    //在这个方法里面 自己对孩子进行排版 不使用系统默认的排版方式

    @Override

    protected void onLayout(boolean changed, int l, int t, int r, int b) {





        //[1]找到menu和main孩子

        View menuView = getChildAt(0);

        View mainView = getChildAt(1);

        //[2]获取菜单的宽度

        menuWidth = menuView.getMeasuredWidth();

        //[3]对menu菜单进行排版

        menuView.layout(-menuWidth, t, l, b);

        //[4]对main界面排版

        mainView.layout(l, t, r, b);





    }



    //当用户横着滑动的距离 > 竖着滑动的距离 就拦截事件 不让孩子处理事件 否则就让孩子处理事件

    @Override

    public boolean onInterceptTouchEvent(MotionEvent ev) {

        switch (ev.getAction()) {

            case MotionEvent.ACTION_DOWN:

                downX = ev.getX();

                downY = ev.getY();





                break;





            case MotionEvent.ACTION_MOVE:

                float moveX = ev.getX();

                float moveY = ev.getY();

                //[1]算出X轴移动的距离 和 Y移动的距离

                int distanceX = (int) (moveX - downX);

                int distanceY = (int) (moveY - downY);

                //[2]如果x轴移动的距离大于Y轴移动的距离 就拦截事件

                if (Math.abs(distanceX) > Math.abs(distanceY)) {

                    return true;

                }

                break;





            case MotionEvent.ACTION_UP:

                break;

        }



        return super.onInterceptTouchEvent(ev);

    }







    //处理当前view的事件

    @Override

    public boolean onTouchEvent(MotionEvent event) {

        switch (event.getAction()) {

            case MotionEvent.ACTION_DOWN:  //按下

                //[1]获取手指按下的坐标

                downX = event.getX();

                break;





            case MotionEvent.ACTION_MOVE: //移动

                //[2]算出移动的距离

                float moveX = event.getX();

                distaceX = (int) (moveX - downX)+currentMenuPosition;

                //[3]对边界进行处理

                if (distaceX <= 0) {

                    distaceX = 0;

                }else if (distaceX >=menuWidth) {

                    distaceX = menuWidth;

                }





                //[4]开始滚动view

                startScrollViewContent(distaceX);

                break;





            case MotionEvent.ACTION_UP:    //抬起

                //[5]当手指抬起后, 如果移动的距离 < 菜单的宽/2 就回到左边  否则像右移动

                if (distaceX < menuWidth/2) {

                    currentMenuPosition = 0;

                }else {

                    currentMenuPosition = menuWidth;

                }



                //[6]当一段逻辑同时用到多次 我们最好做抽取





                int startX = distaceX; //就是移动的距离

                int endX = currentMenuPosition;

                //[7]实现平滑滚动

                startScroller(startX, endX);



                break;

        }









        return true;//让当前view消费事件

    }





    //实现平滑滚动

    private void startScroller(int startX, int endX) {

        int dx = endX - startX;

        int duration = Math.abs(dx)*20;

        //产生模拟的数据

        mScroller.startScroll(startX, 0, dx, 0, duration);

        //调用invalidata 请求绘制

        invalidate();  //---->drawchild ---child.draw --computeScroll()

    }





    /**

     * 由于scrollTo 方法 系统在实现的时候 传入正值往左移动  传入负值往右移动 所以我重写这个方

     *  符合中国 人的思维

     * @param x

     */

    public void startScrollViewContent(int x){

        super.scrollTo(-x, 0);

    }





    //取出我们模拟滚动的数据

    @Override

    public void computeScroll() {

        if (mScroller.computeScrollOffset()) {

            //[1]取出我们模拟的数据

            int currX = mScroller.getCurrX();

            //[2]调用startScrollViewContent 让view的内容滚动

            startScrollViewContent(currX);

            invalidate();





        }

    }



    //通过这个方法控制菜单打开或者关闭

    public void setOnMenuIsOpen() {

        int startX = 0;

        if (currentMenuPosition == 0) {

            //说明菜单是关闭状态  需要打开

            currentMenuPosition = menuWidth;

        }else if (currentMenuPosition == menuWidth) {

            //说明菜单是打开状态  需要关闭

            currentMenuPosition = 0;

            startX = menuWidth;





        }

        //调用平滑滚动的方法

        startScroller(startX, currentMenuPosition);

    }

}















猜你喜欢

转载自blog.csdn.net/Cricket_7/article/details/88732913