码迷,mamicode.com
首页 > 其他好文 > 详细

自定义控件——左右菜单实现的总体思路

时间:2016-05-13 12:54:18      阅读:166      评论:0      收藏:0      [点我收藏+]

标签:

1.创建3个“菜单”视图,并且将“菜单”添加到父视图。

 1   private Context context;
 2     private RelativeLayout leftMenu;
 3     private RelativeLayout middleMenu;
 4     private RelativeLayout rightMenu;
 5     
 6     public MainUI(Context context, AttributeSet attrs) {
 7         super(context, attrs);
 8         initView(context);
 9     }
10     
11     public MainUI(Context context) {
12         super(context);
13         initView(context);
14     }
15     
16     private void initView(Context context) {
17         leftMenu=new RelativeLayout(context);
18         middleMenu=new RelativeLayout(context);
19         rightMenu=new RelativeLayout(context);
20         
21         leftMenu.setBackgroundColor(Color.BLACK);
22         middleMenu.setBackgroundColor(Color.GREEN);
23         rightMenu.setBackgroundColor(Color.BLACK);
24         
25         addView(leftMenu);
26         addView(middleMenu);
27         addView(rightMenu);
28     }

MainUI.java

2.将左右菜单的宽度设置为当前屏幕宽度的百分之八十,高度设为当前屏幕高度的值;中间菜单的宽度和高度设置为当前屏幕的宽高值。

  1) 重载onMeasure()方法,并且测量菜单视图的宽高值。

  3)设置为当前屏幕的百分之八十,需要用到以下方法
    MeasureSpec.getSize(int measureSpec):根据提供的测量值(格式)提取大小值。
        MeasureSpec.makeMeasureSpec(int size,int mode);根据提供的大小值和模式创建一个测量值(格式)。
        size:这里设置为当前长度的百分之八十。
      mode:以怎样的方式测量。MeasureSpec.EXACTLY精准的

 1   //测量屏幕的宽度和高度
 2     @Override
 3     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
 4         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
 5         middleMenu.measure(widthMeasureSpec, heightMeasureSpec); 
  int realwidth=MeasureSpec.getSize(widthMeasureSpec);//取得当前屏幕整体的宽度) 8 int tempWidthMeasure=MeasureSpec.makeMeasureSpec( 9 (int)(realwidth*0.8f), MeasureSpec.EXACTLY);//左菜单和右菜单都为百分之八十 10 int temp=(int) (widthMeasureSpec*0.8f); 11 leftMenu.measure(tempWidthMeasure, heightMeasureSpec); 12 rightMenu.measure(tempWidthMeasure, heightMeasureSpec); 13 }

MainUI.java

3.将“菜单布局”填充到屏幕中。
  1)重载OnLayout()方法.
  2)设置布局的位置:middleMask.layout(l, t, r, b);

    // 填充到位置的哪里
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        middleMenu.layout(l, t, r, b);
        leftMenu.layout(l - leftMenu.getMeasuredWidth(), t, r, b);
        rightMenu.layout(l + middleMenu.getMeasuredWidth(),t,
                l + middleMenu.getMeasuredWidth()
                + rightMenu.getMeasuredWidth(), b);
    }

MainUI.java
 

4.当手指左右滑动的时候,移动“菜单”。

  1)对事件进行处理,判断是否是左右滑动。
    i.  当手指按下的时候,获取初始点。
    ii.  当手指移动的时候获取x轴和y轴的值,取整数。
    iii. 如果x轴移动的值大于20,且x轴移动的值大于y轴移动的值时,则判定是左右滑动。

 1     private boolean isTestCompete;//判断是怎样的一个事件
 2     private boolean isLeftRightEvent;//是否是左右滑动
 3 
 4         //事件分发
 5     @Override
 6     public boolean dispatchTouchEvent(MotionEvent ev) {
 7         if(!isTestCompete){
 8             getEventType(ev);
 9             return true;
10         }
11         return super.dispatchTouchEvent(ev);
12     } 
13 
14     private Point point=new Point();
15     private static final int TEST_DIS=20;
16         
17     //对事件进行处理
18     private void getEventType(MotionEvent ev) {
19         switch (ev.getActionMasked()) {
20         case MotionEvent.ACTION_DOWN://当手指按下的时候,获取初始点
21             point.x=(int) ev.getX();
22             point.y=(int) ev.getY();
23             break;
24         case MotionEvent.ACTION_MOVE://当手指移动的时候
25             int dx=(int) Math.abs(ev.getX()-point.x);//获取x轴移动的值,取正数
26             int dy=(int) Math.abs(ev.getY()-point.y);//获取y轴移动的值,取正数
27             if(dx>=TEST_DIS&&dx>dy){
28                 //左右滑动
29                 isLeftRightEvent=true;
30                 isTestCompete=true;
31                 point.x=(int) ev.getX();
32                 point.y=(int) ev.getY();
33             }else if(dy>=TEST_DIS&&dy>dx){
34                 //上下滑动
35                 isLeftRightEvent=false;
36                 isTestCompete=true;
37                 point.x=(int) ev.getX();
38                 point.y=(int) ev.getY();
39             }
40             break;
41         case MotionEvent.ACTION_UP://当手指离开的时候
42         case MotionEvent.ACTION_CANCEL:
43             break;
44         }
45         
46     }

MainUI.java

  2)如果是左右滑动,则移动“菜单布局”.
    i.  获取要移动的x轴距离(expectX):要移动的距离=移动的距离-滚动的距离。
    ii.  如果expectX小于0,则在expectX和左菜单的长度的负数之间取最大值。
      否则,在expectX和右菜单的长度的正数之间取最小值。
    iii. 移动“菜单布局”到当前的位置。scrollTo(finalX,0);
    iv 重新给初始点赋值。

 1   @Override
 2     public boolean dispatchTouchEvent(MotionEvent ev) {
 3         if(!isTestCompete){
 4             getEventType(ev);
 5             return true;
 6         }
 7         if(isLeftRightEvent){//当用户左右滑动的时候
 8             switch (ev.getActionMasked()) {
 9             case MotionEvent.ACTION_MOVE:
10                 int curScrollX=getScrollX();//滚动的距离
11                 int dis_x=(int) (ev.getX()-point.x);//手指放下到滑动的距离,也就是移动的距离
12                 int expectX=-dis_x+curScrollX;//它两的差值肯定在20之间。
13                 int finalX=0;
14                 if(expectX<0){
15                     finalX=Math.max(expectX, -leftMenu.getMeasuredWidth());
16                 }else{
17                     finalX=Math.min(expectX, rightMenu.getMeasuredWidth());
18                 }
19                 scrollTo(finalX,0);//移动到当前这个位置
20                 point.x=(int) ev.getX();
21                 break;
22             case MotionEvent.ACTION_UP:
23             case MotionEvent.ACTION_CANCEL:
24                 isLeftRightEvent=false;
25                 isTestCompete=false;
26                 break;
27             default:
28                 break;
29             }
30         }else{
31             switch (ev.getActionMasked()) {
32             case MotionEvent.ACTION_UP:
33                 isLeftRightEvent=false;
34                 isTestCompete=false;
35                 break;
36             default:
37                 break;
38             }
39         }
40         return super.dispatchTouchEvent(ev);
41     }
MainUI.java

5.在MainActivity上显示左右菜单的自定义控件。

 1 public class MainActivity extends FragmentActivity {
 2 
 3     private MainUI mainUI;
 4 
 5     @Override
 6     protected void onCreate(Bundle savedInstanceState) {
 7         super.onCreate(savedInstanceState);
 8         mainUI = new MainUI(this);
 9         setContentView(mainUI);
10     }
11 
12 }

做完上面那一步,菜单就可以左右滑动了。不过我们发现它的手指移到一半多的时候,它还是停留在那不动,不会展示出左菜单,用户体验不是很好,我们应该加入左右移动的动画。

6.当手指离开屏幕且手指移动超过左右菜单的一半时,就让它自动滑动,出现左菜单或是右菜单。
   当手指离开屏幕且手指移动不超过左右菜单的一半时,就让它自动滑动回去,显示中间的菜单。

  1)定义Scroller:Scroller mScoller=new Scroller(context, new DecelerateInterpolator());

  private Scroller mScoller;
    
    private void initView(Context context){
        ......
        mScoller=new Scroller(context, new DecelerateInterpolator());
    }
MainUI.java
 

   2)手指离开屏幕的时候使用动画控制偏移过程 。

    在duration秒内开始一个动画控制Scoller.startScroll(startX, startY, dx, dy, duration);

 1   //事件分发
 2     @Override
 3     public boolean dispatchTouchEvent(MotionEvent ev) {
 4         if(!isTestCompete){
 5             getEventType(ev);
 6             return true;
 7         }
 8         if(isLeftRightEvent){//当用户左右滑动的时候
 9             switch (ev.getActionMasked()) {
10             case MotionEvent.ACTION_MOVE:
           int curScorllX=getScrollX();
11 ......
           break; 23 case MotionEvent.ACTION_UP: 24 case MotionEvent.ACTION_CANCEL: 25 curScrollX=getScrollX(); 26 //当移动超过左右菜单一半的时候,让它自动滑动,出现左右菜单 27 //leftMenu.getMeasuredHeight()>>1等于leftMenu.getMeasuredHeight()/2 28 if(Math.abs(curScrollX)>leftMenu.getMeasuredHeight()>>1){ 29 if(curScrollX<0){//向左滑动 30 //使用动画控制偏移过程 31 mScoller.startScroll(curScrollX, 0, 32 -leftMenu.getMeasuredWidth()-curScrollX, 0,200);//从手指那一块开始执行 33 }else{ 34 mScoller.startScroll(curScrollX, 0, 35 leftMenu.getMeasuredWidth()-curScrollX, 0,200); 36 } 37 }else{//如果当移动没有超过左右菜单一半,则让它自动滑动回去 38 mScoller.startScroll(curScrollX, 0, -curScrollX, 0,200); 39 } 40 //我们需要刷新,所以我们需要调用invalidate()方法,手动重新绘制。 41 invalidate(); 42 isLeftRightEvent=false; 43 isTestCompete=false; 44 break;

47 } 48 }else{ 49   ......
 } 58 return super.dispatchTouchEvent(ev); 59 }

MainUI.java

 3)重写computeScroll()方法:如果滚动尚未完成,则调用scrollTo(mScoller.getCurrX(), 0)方法平滑移动到该坐标处。

     /**
     * 为了实现偏移 控制,一般自定义View/ViewGroup都需要重载该方法
     * 该方法由父视图调用用来请求子视图根据偏移值mScrollX,mScrollY重新绘制。
     */
    @Override
    public void computeScroll() {
        super.computeScroll();
        if(!mScoller.computeScrollOffset()){//判断滚动是否完成
            return;
        }
        int tempmX=mScoller.getCurrX();
        scrollTo(tempmX, 0);
    } 

MainUI.java
 

7.处理点击事件:
  1)定义一个LeftMenu类,继承Fragment。

 1 public class LeftMenu extends Fragment {
 2     
 3     @Override
 4     public View onCreateView(LayoutInflater inflater, ViewGroup container,
 5             Bundle savedInstanceState) {
 6         View v=inflater.inflate(R.layout.left, container,false);
 7         v.findViewById(R.id.button1).setOnClickListener(new View.OnClickListener() {
 8             
 9             @Override
10             public void onClick(View v) {
11                 System.out.println("com.morag.mymenu");
12                 
13             }
14         });
15         return v;
16     }
17 
18 } 
left.xml
<?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" >

    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button" />

</LinearLayout>

  2)在MainUi.java中给leftMenu添加一个id。

    public static final int LEFT_ID=0xaabbcc;
    public static final int RIGHT_ID=0xaaccbb;
    public static final int MIDDLE_ID=0xbbaacc;
    private void initView(Context context){
        ......
        
        leftMenu.setId(LEFT_ID);
        rightMenu.setId(RIGHT_ID);
        middleMenu.setId(MIDDLE_ID);
        
    }
MainUI.java

  3)让MainActivity继承FragmentAcitivy,并且添加LeftMneu碎片,提交业务处理

public class MainActivity extends FragmentActivity {

    private MainUI mainUI;
    private LeftMenu leftMenu;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mainUI = new MainUI(this);
        setContentView(mainUI);
        leftMenu = new LeftMenu();
        getSupportFragmentManager().beginTransaction()
                .add(mainUI.LEFT_ID, leftMenu).commit();
    }

}

   4)到这一步,发现那个左菜单的Button还是不能点击。怎么会事?

    在手指按下和手指离开时,

 super.dispatchTouchEvent(ev);无效的手势
  private void getEventType(MotionEvent ev) {
        switch (ev.getActionMasked()) {
        case MotionEvent.ACTION_DOWN://当手指按下的时候,获取初始点
            point.x=(int) ev.getX();
            point.y=(int) ev.getY();
            super.dispatchTouchEvent(ev);//无效的手势
            break;
        case MotionEvent.ACTION_MOVE://当手指移动的时候
            ......
        case MotionEvent.ACTION_UP://当手指离开的时候
        case MotionEvent.ACTION_CANCEL:
            super.dispatchTouchEvent(ev);//分发TouchEvent,无效的手势
            isLeftRightEvent=false;
            isTestCompete=false;
            break;
        }
        
    }
MainUI.java

8.添加蒙版效果
  1)在上面盖一层LinearLaout。
  2)设置背景颜色、大小、位置。
  4)让蒙版添加渐变效果
    i.重载scrollTo(int x,int y)方法,它会根据手指的变化而变化。
    ii.蒙版设置不透明度:不透明度=X轴滚动距离的正值/左右菜单的宽。

这个最后一步了,直接上MainUI.java的全部代码吧。

  1 package com.morag.mymenu;
  2 
  3 import android.content.Context;
  4 import android.graphics.Color;
  5 import android.graphics.Point;
  6 import android.util.AttributeSet;
  7 import android.view.MotionEvent;
  8 import android.view.animation.DecelerateInterpolator;
  9 import android.widget.FrameLayout;
 10 import android.widget.LinearLayout;
 11 import android.widget.RelativeLayout;
 12 import android.widget.Scroller;
 13 
 14 /**
 15  * 实现左右菜单的自定义布局
 16  *
 17  */
 18 public class MainUI extends RelativeLayout{
 19     
 20     private Context context;
 21     private FrameLayout leftMenu;
 22     private FrameLayout middleMenu;
 23     private FrameLayout rightMenu;
 24     private LinearLayout middleMask;
 25     
 26     private Point point=new Point();
 27     private static final int TEST_DIS=20;
 28     
 29     
 30     public static final int LEFT_ID=0xaabbcc;
 31     public static final int RIGHT_ID=0xaaccbb;
 32     public static final int MIDDLE_ID=0xbbaacc;
 33     public MainUI(Context context) {
 34         super(context);
 35         initView(context);
 36     }
 37     
 38     
 39     //注意因为是使用自定义控件,会在layout xml里 注册使用,所以必须创建该构造函数
 40     public MainUI(Context context, AttributeSet attrs) {
 41         super(context, attrs);
 42         initView(context);
 43     }
 44     
 45     private Scroller mScoller;
 46     
 47     private void initView(Context context){
 48         this.context=context;
 49         mScoller=new Scroller(context, new DecelerateInterpolator());
 50         leftMenu=new FrameLayout(context);
 51         middleMenu=new FrameLayout(context);
 52         rightMenu=new FrameLayout(context);
 53         middleMask=new LinearLayout(context);
 54         
 55         leftMenu.setBackgroundColor(Color.RED);
 56         middleMenu.setBackgroundColor(Color.GREEN);
 57         rightMenu.setBackgroundColor(Color.RED);
 58         middleMask.setBackgroundColor(0x88000000);
 59         
 60         leftMenu.setId(LEFT_ID);
 61         rightMenu.setId(RIGHT_ID);
 62         middleMenu.setId(MIDDLE_ID);
 63         
 64         //添加3个菜单
 65         addView(leftMenu);
 66         addView(middleMenu);
 67         addView(rightMenu);
 68         addView(middleMask);
 69         middleMask.setAlpha(0);
 70     }
 71     
 72     public float onMiddleMask(){
 73         return middleMask.getAlpha();
 74     }
 75     
 76     //它会根据手指滑动距离的变化而变化
 77     @Override
 78     public void scrollTo(int x, int y) {
 79         super.scrollTo(x, y);
 80         int curX=Math.abs(getScrollX());
 81         float scale=curX/(float)leftMenu.getMeasuredWidth();//变化值
 82         middleMask.setAlpha(scale);//设置透明度
 83 //        System.out.println("透明度"+middleMask.getAlpha());
 84     }
 85     
 86     //测量屏幕的宽度和高度
 87     @Override
 88     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
 89         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
 90         middleMenu.measure(widthMeasureSpec, heightMeasureSpec);
 91         middleMask.measure(widthMeasureSpec, heightMeasureSpec);
 92         int realwidth=MeasureSpec.getSize(widthMeasureSpec);//取得当前屏幕整体的宽度
 93         /*MeasureSpec.makeMeasureSpec(size, mode)
 94          * size:这里设置为当前长度的百分之八十。
 95          * mode:以怎样的方式测量.MeasureSpec.EXACTLY精准的
 96          */
 97         int tempWidthMeasure=MeasureSpec.makeMeasureSpec(
 98                 (int)(realwidth*0.8f), MeasureSpec.EXACTLY);//左菜单和右菜单都为百分之八十

102 leftMenu.measure(tempWidthMeasure, heightMeasureSpec); 103 rightMenu.measure(tempWidthMeasure, heightMeasureSpec); 104 } 105 106 //填充到位置的哪里 107 @Override 108 protected void onLayout(boolean changed, int l, int t, int r, int b) { 109 super.onLayout(changed, l, t, r, b); 110 middleMenu.layout(l, t, r, b); 111 middleMask.layout(l, t, r, b); 112 leftMenu.layout(l-leftMenu.getMeasuredWidth(), t, r, b); 113 rightMenu.layout(l+middleMenu.getMeasuredWidth(), t, 114 l+middleMenu.getMeasuredWidth()+rightMenu.getMeasuredWidth(), b); 115 } 116 117 private boolean isTestCompete;//判断是怎样的一个事件 118 private boolean isLeftRightEvent;//是否是左右滑动 119 120 //事件分发 121 @Override 122 public boolean dispatchTouchEvent(MotionEvent ev) { 123 if(!isTestCompete){ 124 getEventType(ev); 125 return true; 126 } 127 if(isLeftRightEvent){//当用户左右滑动的时候 128 switch (ev.getActionMasked()) { 129 case MotionEvent.ACTION_MOVE: 130 int curScrollX=getScrollX();//滚动的距离 131 int dis_x=(int) (ev.getX()-point.x);//手指放下以及滑动的距离 132 int expectX=-dis_x+curScrollX; 133 int finalX=0; 134 if(expectX<0){ 135 finalX=Math.max(expectX, -leftMenu.getMeasuredWidth()); 136 }else{ 137 finalX=Math.min(expectX, rightMenu.getMeasuredWidth()); 138 } 139 scrollTo(finalX,0);//移动到当前这个位置 140 point.x=(int) ev.getX(); 141 break; 142 case MotionEvent.ACTION_UP: 143 case MotionEvent.ACTION_CANCEL: 144 curScrollX=getScrollX(); 145 //当移动超过左右菜单一半的时候,让它自动滑动,出现左右菜单 146 //leftMenu.getMeasuredHeight()>>1等于leftMenu.getMeasuredHeight()/2 147 if(Math.abs(curScrollX)>leftMenu.getMeasuredHeight()>>1){ 148 if(curScrollX<0){//向左滑动 149 //使用动画控制偏移过程 150 mScoller.startScroll(curScrollX, 0, 151 -leftMenu.getMeasuredWidth()-curScrollX, 0,200); 152 }else{ 153 mScoller.startScroll(curScrollX, 0, 154 leftMenu.getMeasuredWidth()-curScrollX, 0,200); 155 } 156 }else{//如果当移动没有超过左右菜单一半,则让它自动滑动回去 157 mScoller.startScroll(curScrollX, 0, -curScrollX, 0,200); 158 } 159 //我们需要刷新,所以我们需要调用invalidate()方法,手动重新绘制。 160 invalidate(); 161 isLeftRightEvent=false; 162 isTestCompete=false; 163 break; 164 default: 165 break; 166 } 167 }else{ 168 switch (ev.getActionMasked()) { 169 case MotionEvent.ACTION_UP: 170 isLeftRightEvent=false; 171 isTestCompete=false; 172 break; 173 default: 174 break; 175 } 176 } 177 return super.dispatchTouchEvent(ev); 178 } 179 180 /** 181 * 为了实现偏移 控制,一般自定义View/ViewGroup都需要重载该方法 182 * 该方法由父视图调用用来请求子视图根据偏移值mScrollX,mScrollY重新绘制。 183 */ 184 @Override 185 public void computeScroll() { 186 super.computeScroll(); 187 if(!mScoller.computeScrollOffset()){//判断滚动是否完成 188 return; 189 } 190 int tempmX=mScoller.getCurrX(); 191 scrollTo(tempmX, 0); 192 } 193 194 195 //对事件进行处理 196 private void getEventType(MotionEvent ev) { 197 switch (ev.getActionMasked()) { 198 case MotionEvent.ACTION_DOWN://当手指按下的时候,获取初始点 199 point.x=(int) ev.getX(); 200 point.y=(int) ev.getY(); 201 super.dispatchTouchEvent(ev);//无效的手势 202 break; 203 case MotionEvent.ACTION_MOVE://当手指移动的时候 204 int dx=(int) Math.abs(ev.getX()-point.x);//获取x轴移动的值,取正数 205 int dy=(int) Math.abs(ev.getY()-point.y);//获取y轴移动的值,取正数 206 if(dx>=TEST_DIS&&dx>dy){ 207 //左右滑动 208 isLeftRightEvent=true; 209 isTestCompete=true; 210 point.x=(int) ev.getX(); 211 point.y=(int) ev.getY(); 212 }else if(dy>=TEST_DIS&&dy>dx){ 213 //上下滑动 214 isLeftRightEvent=false; 215 isTestCompete=true; 216 point.x=(int) ev.getX(); 217 point.y=(int) ev.getY(); 218 } 219 break; 220 case MotionEvent.ACTION_UP://当手指离开的时候 221 case MotionEvent.ACTION_CANCEL: 222 super.dispatchTouchEvent(ev);//分发TouchEvent,无效的手势 223 isLeftRightEvent=false; 224 isTestCompete=false; 225 break; 226 } 227 228 } 229 230 }

 

自定义控件——左右菜单实现的总体思路

标签:

原文地址:http://www.cnblogs.com/morag/p/5487384.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!