Android之高仿易信“侧滑菜单(左侧)”

昨天刚弄完侧滑菜单的实现,就打算仿照一个易信的侧滑菜单来练练手,也顺便来展示一下框架的重要性~~

首先还是效果图:

Android之高仿易信“侧滑菜单(左侧)”_第1张图片

Android之高仿易信“侧滑菜单(左侧)”_第2张图片

Android之高仿易信“侧滑菜单(左侧)”_第3张图片


其实实现起来很简单,只需要分别自定义左边的菜单布局文件和右边的内容布局文件即可,至于侧滑菜单的实现,可以看看上一篇文章,里面有详细解释,在这里,就不侧重讲解了,今天侧重于框架的使用~~

其中,对于易信圆形头像需要自定义一个ImageView来实现,这需要大家掌握,因为十分有用。


一、左边Menu的布局实现:

layout\menu.xml




    
    
    
    
    
   
    
    
    
    
    
    

    
    
    
    
         
    
        
   
   
   
   
   
    
         
    
        
   
   
   
   
   
    
         
    
        
   
   
   
   
   
    
         
    
        
   
   
   
   
   
    
         
    
        
   
  
  
  
  

  
  
  
  
  

二、右边content的布局实现:

layout\content.xml



    
    


        

        

        

    
    
       
        



三、侧滑菜单的布局实现:

layout\main.xml

  
  
      
         
        
        
        "
        
   
      
  
  

四、侧滑菜单java代码:

SlidingLayout.java

package t.first;

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.LinearLayout;

/** 
 * @ 对外仅需设置的接口
 * 
 * -判断左侧布局是否完全显示出来,或完全隐藏,滑动过程中此值无效
 * boolean SlidingLayout.isLeftLayoutVisible() 
 * 
 * -将屏幕滚动到右侧布局界面
 * void SlidingLayout.scrollToRightLayout()
 * 
 * -将屏幕滚动到左侧布局界面	
 * void SlidingLayout.scrollToLeftLayout() 
 * 
 */


public class SlidingLayout extends LinearLayout {

	private static final int SNAP_VELOCITY = 200;  //滚动显示和隐藏左侧布局时,手指滑动需要达到的速度。	
	private VelocityTracker mVelocityTracker;      //用于计算手指滑动的速度。
	private int touchSlop;                         //在被判定为滚动之前用户手指可以移动的最大值。
    private int screenWidth;                       //屏幕宽度值
	private int leftEdge ;                         //左边最多可以滑动到的左边缘,值由左边布局的宽度来定,marginLeft到达此值之后,不能再减少。
    private int rightEdge = 0;                     //左边最多可以滑动到的右边缘,值恒为0,即marginLeft到达0之后,不能增加。
    private float xDown;                           //记录手指按下时的横坐标。
    private float yDown;                           //记录手指按下时的纵坐标。
    private float xMove;                           //记录手指移动时的横坐标。
    private float yMove;                           //记录手指移动时的纵坐标。
    private float xUp;                             //记录手机抬起时的横坐标。
    private boolean isLeftLayoutVisible;           //左侧布局当前是显示还是隐藏。只有完全显示或隐藏时才会更改此值,滑动过程中此值无效。
    private boolean isSliding;                     //是否正在滑动。
    private MarginLayoutParams leftLayoutParams;   //左侧布局的参数,通过此参数来重新确定左侧布局的宽度,以及更改leftMargin的值。
    private MarginLayoutParams rightLayoutParams;  //右侧布局的参数,通过此参数来重新确定右侧布局的宽度。
    private View leftLayout;                       //左侧布局对象。
    private View rightLayout;                      //右侧布局对象。
     
     
    //构造函数
	public SlidingLayout(Context context, AttributeSet attrs) 
	{
		super(context, attrs);
		
		
		//获取屏幕宽度
		WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);		
		screenWidth = wm.getDefaultDisplay().getWidth();
		
        //获取滑动的最短距离
		touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
				
		
	}

	//创建VelocityTracker对象,并将触摸事件加入到VelocityTracker当中。
    private void createVelocityTracker(MotionEvent event) 
	{
	    if (mVelocityTracker == null)
		{
			mVelocityTracker = VelocityTracker.obtain();
		}
			
		mVelocityTracker.addMovement(event);
	}
	
   
    
    //拦截触摸事件
    @Override  
    public boolean onInterceptTouchEvent(MotionEvent event) {  
         
    	
    	switch(event.getAction())
    	{  
        case MotionEvent.ACTION_DOWN:
        	xDown = event.getRawX();
			yDown = event.getRawY();
			
					
			//当左边菜单完全显示时,点击右边的View,我们希望是拦截,而左边不拦截
			if(xDown > leftLayout.getLeft()+leftLayout.getWidth() && isLeftLayoutVisible)
				return true;
			
        	break;  
        	
        case MotionEvent.ACTION_MOVE:  
        	
            int distanceX=(int) (event.getRawX()-xDown);
                                    
            //水平滑动距离超过一定距离,拦截所有子view的touch事件,来处理自身onTouch事件来达到滑动的目的  
            if(Math.abs(distanceX)>=touchSlop)  
            {            
                return true;                                  
            }  
            break;    	   
        }  
      	
        return false;  
    }  
    
    
    //触摸事件
    @Override
    public boolean onTouchEvent(MotionEvent event) 
	{
		// TODO 自动生成的方法存根
				
		createVelocityTracker(event);
				
		switch (event.getAction()) 
		{
		case MotionEvent.ACTION_DOWN:
			// 手指按下时,记录按下时的横坐标
			xDown = event.getRawX();
			yDown = event.getRawY();
					
			break;
					
		case MotionEvent.ACTION_MOVE:
			// 手指移动时,对比按下时的横坐标,计算出移动的距离,来调整右侧布局的leftMargin值,从而显示和隐藏左侧布局
			xMove = event.getRawX();
			yMove = event.getRawY();
			int distanceX = (int) (xMove - xDown);
			int distanceY = (int) (yMove - yDown);
			
			//只有触摸右边的View,才发生滑动
			if(xMove > leftLayout.getLeft()+leftLayout.getWidth()) 
			{
				
			//向右滑
			if (!isLeftLayoutVisible && distanceX >= touchSlop && (isSliding || Math.abs(distanceY) <= touchSlop)) 
			{
				isSliding = true;
						
				leftLayoutParams.leftMargin = leftEdge + distanceX;  
						
				if (leftLayoutParams.leftMargin > rightEdge) 
				{  
			          leftLayoutParams.leftMargin = rightEdge;  
			    }  
						  
			}
			//向左滑
		    else if (isLeftLayoutVisible && -distanceX >= touchSlop) 
			{
				isSliding = true;
						
				leftLayoutParams.leftMargin = distanceX; 
														
				if (leftLayoutParams.leftMargin < leftEdge) 
		        {  
			          leftLayoutParams.leftMargin = leftEdge;  
			    }
					
		    }
					
			leftLayout.setLayoutParams(leftLayoutParams);  
			
			}
					
		    break;  
					
					
		case MotionEvent.ACTION_UP:
			xUp = event.getRawX();
			int upDistanceX = (int) (xUp - xDown);
					
			if (isSliding) 
			{
				// 手指抬起时,进行判断当前手势的意图,从而决定是滚动到左侧布局,还是滚动到右侧布局
				if (wantToShowLeftLayout())
				{
					if (shouldScrollToLeftLayout())
					{
						scrollToLeftLayout();
					} 
					else
					{
						scrollToRightLayout();
					}
				} 
				else if (wantToShowRightLayout()) 
				{
					if (shouldScrollToRightLayout())
					{
						scrollToRightLayout();
					} 
					else 
					{
						scrollToLeftLayout();
					}
				}
			}
			//在左侧菜单完全显示时,我们希望的是点击右边的View可以发生恢复
			else if (upDistanceX < touchSlop && isLeftLayoutVisible && xUp > leftLayout.getLeft()+leftLayout.getWidth()) 
			{
				scrollToRightLayout();
			}
										
			recycleVelocityTracker(); 
					
			break;
					
		    }
	
			if (this.isEnabled()) 
			{
				 if (isSliding) 
				 {
				     unFocusBindView();
					 return true;
				 }
				 if (isLeftLayoutVisible) 
				 {
					 return true;
				 }
				 return false;
			}
			
			return true;
	}
    
    
	//将屏幕滚动到左侧布局界面,滚动速度设定为50.	
	public void scrollToLeftLayout() 
	{
		//传入第一个参数
		new ScrollTask().execute(50);
	}

	
	//将屏幕滚动到右侧布局界面,滚动速度设定为-50.
	public void scrollToRightLayout() 
	{
		//传入第一个参数
		new ScrollTask().execute(-50);
	}

	//左侧布局是否完全显示出来,或完全隐藏,滑动过程中此值无效。	 
	public boolean isLeftLayoutVisible() 
	{
		return isLeftLayoutVisible;
	}

	//在onLayout中重新设定左侧布局和右侧布局的参数。	
	@Override
	protected void onLayout(boolean changed, int l, int t, int r, int b)
	{
		super.onLayout(changed, l, t, r, b);
		
		if (changed) 
		{
						
			// 获取左侧布局对象  
            leftLayout = getChildAt(0);  
            leftLayoutParams = (MarginLayoutParams) leftLayout.getLayoutParams();  
            // 设置最左边距为负的左侧布局的宽度  
            leftEdge = -leftLayoutParams.width;  
            leftLayoutParams.leftMargin = leftEdge;  
            leftLayout.setLayoutParams(leftLayoutParams); 
            
            // 获取右侧布局对象  
            rightLayout = getChildAt(1);  
            rightLayoutParams = (MarginLayoutParams) rightLayout.getLayoutParams();  
            rightLayoutParams.width = screenWidth;  
            rightLayout.setLayoutParams(rightLayoutParams); 
            rightLayout.setClickable(true);
		
		}		
		
	}


	//判断当前手势的意图是不是想显示右侧布局。如果手指移动的距离是负数,且当前左侧布局是可见的,则认为当前手势是想要显示右侧布局。	
	private boolean wantToShowRightLayout()
	{
		return xUp - xDown < 0 && isLeftLayoutVisible;
	}

	
	//判断当前手势的意图是不是想显示左侧布局。如果手指移动的距离是正数,且当前左侧布局是不可见的,则认为当前手势是想要显示左侧布局。	
	private boolean wantToShowLeftLayout()
    {
		return xUp - xDown > 0 && !isLeftLayoutVisible;
	}

	//判断是否应该滚动将左侧布局展示出来。如果手指移动距离大于屏幕的1/2,或者手指移动速度大于SNAP_VELOCITY,就认为应该滚动将左侧布局展示出来。
	private boolean shouldScrollToLeftLayout() 
	{
		return xUp - xDown > leftLayoutParams.width / 2 || getScrollVelocity() > SNAP_VELOCITY;
	}

	//判断是否应该滚动将右侧布局展示出来。如果手指移动距离加上leftLayoutPadding大于屏幕的1/2,或者手指移动速度大于SNAP_VELOCITY, 就认为应该滚动将右侧布局展示出来。
	private boolean shouldScrollToRightLayout() 
	{
		return xDown - xUp > leftLayoutParams.width / 2 || getScrollVelocity() > SNAP_VELOCITY;
	}

		
	//获取手指在右侧布局的监听View上的滑动速度。	 
	private int getScrollVelocity() 
	{
		mVelocityTracker.computeCurrentVelocity(1000);
		int velocity = (int) mVelocityTracker.getXVelocity();
				
		return Math.abs(velocity);
	}

	//回收VelocityTracker对象。	
	private void recycleVelocityTracker() 
	{
		mVelocityTracker.recycle();
		mVelocityTracker = null;
	}

	
	//使用可以获得焦点的控件在滑动的时候失去焦点。
	private void unFocusBindView() 
	{
		if (rightLayout != null) 
		{
			rightLayout.setPressed(false);
			rightLayout.setFocusable(false);
			rightLayout.setFocusableInTouchMode(false);
		}
	}

	
//*********************************************************************************************************************
	
	/**
	 * @ 利用轻量级线程实现滑动 ,应用在手指松开的滑动动画
	 */
	class ScrollTask extends AsyncTask 
	{

		//后台处理,入口参数对应第一个参数类型
		@Override
		protected Integer doInBackground(Integer... speed) {
						 
			int leftMargin = leftLayoutParams.leftMargin; 
						
			// 根据传入的速度来滚动界面,当滚动到达左边界或右边界时,跳出循环。
			while (true) 
			{
				
				leftMargin = leftMargin + speed[0];  
				if (leftMargin > rightEdge) 
				{  
                    leftMargin = rightEdge;  
                    break;  
                }  
                if (leftMargin < leftEdge) 
                {  
                    leftMargin = leftEdge;  
                    break;  
                } 
                
                //主动回调onProgressUpdate来更新界面(滑动)
                publishProgress(leftMargin);  
				
				//使当前线程睡眠指定的毫秒数,为了要有滚动效果产生,每次循环使线程睡眠10毫秒,这样肉眼才能够看到滚动动画。
				try 
				{
					Thread.sleep(10);
				} 
				catch (InterruptedException e) 
				{
					e.printStackTrace();
				}
				
			}
			if (speed[0] > 0) 
			{
				isLeftLayoutVisible = true;
			} 
			else 
			{
				isLeftLayoutVisible = false;
			}
			
			isSliding = false;
			
			//返回对应第三个参数类型,并且把值传入onPostExecute			
			return leftMargin;
		}

		//调用publishProgress时,回调这个方法,用来更新界面(滑动),入口参数对应第二个参数类型
		@Override
		protected void onProgressUpdate(Integer... leftMargin) 
		{
			
			leftLayoutParams.leftMargin = leftMargin[0];  
            leftLayout.setLayoutParams(leftLayoutParams);  
            
			//使用可以获得焦点的控件在滑动的时候失去焦点。
			unFocusBindView();
		}

		//在doInBackground执行完成后执行界面更新,入口参数对应第三个参数类型
		@Override
		protected void onPostExecute(Integer leftMargin) 
		{			
			leftLayoutParams.leftMargin = leftMargin;  
	        leftLayout.setLayoutParams(leftLayoutParams);  
		}
	}

}

五、圆形图像的java代码实现:

RoundedImageView.java

package t.first;


import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff.Mode;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.widget.ImageView;


public class RoundedImageView extends ImageView{

	//构造函数1
	public RoundedImageView(Context context) {
		super(context);
		// TODO 自动生成的构造函数存根
	}
	
	//构造函数2
	public RoundedImageView(Context context, AttributeSet attrs) {
		super(context, attrs);
	}

	//构造函数3
	public RoundedImageView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
	}
	
	@Override
	protected void onDraw(Canvas canvas) 
	{

		Drawable drawable = getDrawable();
						
		//转换为32位ARGB位图
		Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap().copy(Bitmap.Config.ARGB_8888, true);

		//得到该圆形位图
		Bitmap roundBitmap = getCroppedBitmap(bitmap, getWidth());
		
		canvas.drawBitmap(roundBitmap, 0, 0, null);

	}
	
	//转换圆形位图算法
	public static Bitmap getCroppedBitmap(Bitmap bmp, int radius) 
	{
		Bitmap bmp2;
		
		if (bmp.getWidth() != radius || bmp.getHeight() != radius)
			bmp2 = Bitmap.createScaledBitmap(bmp, radius, radius, false);
		else
			bmp2 = bmp;
		
		//创建用于输出结果的32位位图
		Bitmap output = Bitmap.createBitmap(bmp2.getWidth(), bmp2.getHeight(),Config.ARGB_8888);			
		//为输出的位图创建一个画布
		Canvas canvas = new Canvas(output);
        //创建画笔
		Paint paint = new Paint();

		
		//设置画笔属性
		paint.setAntiAlias(true);
		paint.setFilterBitmap(true);
		paint.setDither(true);
		paint.setColor(Color.parseColor("#BAB399"));
		
		//设置画布背景ARGB
		canvas.drawARGB(0, 0, 0, 0);
		//画一个圆作为底层
		canvas.drawCircle(bmp2.getWidth() / 2 + 0.7f,bmp2.getHeight() / 2 + 0.7f, bmp2.getWidth() / 2 + 0.1f, paint);
		
		//设置画笔规则,取两层绘制交集,显示上层。
		paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
		
		//把原图画在已经画了圆形的画布上作为上层,取交集上层,则截取了圆形的原图
		canvas.drawBitmap(bmp2, new Rect(0, 0, bmp2.getWidth(), bmp2.getHeight()), new Rect(0, 0, bmp2.getWidth(), bmp2.getHeight()), paint);

		return output;
	}

	

}


六、MainActivity.java

package t.first;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ImageButton;
import android.widget.RelativeLayout;
import android.widget.TextView;

public class MainActivity extends Activity {
    /** Called when the activity is first created. */
	
	
	private RelativeLayout yixin;
	private RelativeLayout friend;
	private RelativeLayout map;
	private RelativeLayout more;
	private RelativeLayout setting;
	private TextView title;
	private ImageButton menu;
	private SlidingLayout slidingLayout;
	
	
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        slidingLayout = (SlidingLayout) findViewById(R.id.slidingLayout);
        yixin = (RelativeLayout) findViewById(R.id.yixin);
        friend = (RelativeLayout) findViewById(R.id.friend);
        map = (RelativeLayout) findViewById(R.id.map);
        more = (RelativeLayout) findViewById(R.id.more);
        setting = (RelativeLayout) findViewById(R.id.setting);
        title = (TextView) findViewById(R.id.ivTitleName);
        menu = (ImageButton) findViewById(R.id.ivTitleBtnLeft);
        
        yixin.setSelected(true);
        
        yixin.setOnClickListener(new OnClickListener() {
			
			public void onClick(View v) {
				// TODO 自动生成的方法存根
				
				yixin.setSelected(true);
				friend.setSelected(false);
				map.setSelected(false);
				more.setSelected(false);
				setting.setSelected(false);
				title.setText("易信");
				slidingLayout.scrollToRightLayout();
				
			}
		});
        
       friend.setOnClickListener(new OnClickListener() {
			
			public void onClick(View v) {
				// TODO 自动生成的方法存根
				
				yixin.setSelected(false);
				friend.setSelected(true);
				map.setSelected(false);
				more.setSelected(false);
				setting.setSelected(false);
				title.setText("朋友圈");
				slidingLayout.scrollToRightLayout();
				
			}
		});
       
       map.setOnClickListener(new OnClickListener() {
			
			public void onClick(View v) {
				// TODO 自动生成的方法存根
				
				yixin.setSelected(false);
				friend.setSelected(false);
				map.setSelected(true);
				more.setSelected(false);
				setting.setSelected(false);
				title.setText("朋友地图");
				slidingLayout.scrollToRightLayout();
				
			}
		});
       
       more.setOnClickListener(new OnClickListener() {
			
			public void onClick(View v) {
				// TODO 自动生成的方法存根
				
				yixin.setSelected(false);
				friend.setSelected(false);
				map.setSelected(false);
				more.setSelected(true);
				setting.setSelected(false);
				title.setText("更多应用");
				slidingLayout.scrollToRightLayout();
				
			}
		});
       
       setting.setOnClickListener(new OnClickListener() {
			
			public void onClick(View v) {
				// TODO 自动生成的方法存根
				
				yixin.setSelected(false);
				friend.setSelected(false);
				map.setSelected(false);
				more.setSelected(false);
				setting.setSelected(true);
				title.setText("设置");
				slidingLayout.scrollToRightLayout();
				
			}
		});
       
       menu.setOnClickListener(new OnClickListener() {
		
		public void onClick(View v) {
			// TODO 自动生成的方法存根
			
			if (slidingLayout.isLeftLayoutVisible())
			{
				slidingLayout.scrollToRightLayout();
			} 
			else 
			{
				slidingLayout.scrollToLeftLayout();
			}
			
		}
	});
        
        
    }
}

七、左边菜单Item选择器:

drawable\selector_slidingmenu_item.xml




    
       
    
       
    
    



八、左边菜单按钮选择器

drawable\selector_slidingmenu_btn.xml




    
    
    


九、右边内容标题栏左按钮选择器

drawable\selector_title_left.xml




    
    
    
    
    


十、右边内容标题栏右边按钮选择器

drawable\selector_title_right.xml



   
    
    
    
    
    
    


十一、右边内容标题栏正中间字体样式

value\styles.xml




     
    

十一、素材

Android之高仿易信“侧滑菜单(左侧)”_第4张图片


你可能感兴趣的:(Android,android,易信,侧滑菜单)