首页 > 代码库 > Android自己定义组件系列【3】——自己定义ViewGroup实现側滑

Android自己定义组件系列【3】——自己定义ViewGroup实现側滑

有关自己定义ViewGroup的文章已经非常多了,我为什么写这篇文章,对于刚開始学习的人或者对自己定义组件比較生疏的朋友尽管能够拿来主义的用了,可是要一步一步的实现和了解当中的过程和原理才干真真脱离别人的代码,举一反三却不easy,非常多博主事实上不愿意一步一步的去写,这样非常耗时,可是假设能对读者有帮助,能从这篇文章中学会自己定义组件就达到我的目的了。

第一步:搭建框架来实现一个3/5和2/5分屏的界面,效果例如以下:


最外层是一个自己定义的ViewGroup布局文件例如以下:

package com.example.testscrollto;

import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.view.View.MeasureSpec;

public class MyScrollView extends ViewGroup{
	
	private int mWidth;
	private int mHeight;
	
	private float mMenuWeight = 3.0f / 5; //菜单界面比例
	
	private View mMenuView;   //菜单界面
	private View mPriView;	  //内容界面
	
	public MyScrollView(Context context, AttributeSet attrs) {
		super(context, attrs);
		
	}

	@Override
	protected void onLayout(boolean arg0, int arg1, int arg2, int arg3, int arg4) {
		System.out.println("运行了onLayout");
		mMenuView.layout(0, 0, (int)(mWidth * mMenuWeight), mHeight);
		mPriView.layout((int)(mWidth * mMenuWeight), 0, mWidth, mHeight);
	}
	
	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		System.out.println("运行了onMeasure");
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
		/*
		 * onMeasure传入的两个參数是由上一层控件传入的大小,有多种情况,重写该方法时须要对计算控件的实际大小,
		 * 然后调用setMeasuredDimension(int, int)设置实际大小。
		 * onMeasure传入的widthMeasureSpec和heightMeasureSpec不是一般的尺寸数值,而是将模式和尺寸组合在一起的数值。
		 * 我们须要通过int mode = MeasureSpec.getMode(widthMeasureSpec)得到模式,
		 * 用int size = MeasureSpec.getSize(widthMeasureSpec)得到尺寸。
		 * mode共同拥有三种情况,取值分别为MeasureSpec.UNSPECIFIED, MeasureSpec.EXACTLY, MeasureSpec.AT_MOST。
		 * MeasureSpec.EXACTLY是精确尺寸,当我们将控件的layout_width或layout_height指定为详细数值时如andorid:layout_width="50dip",或者为FILL_PARENT是,都是控件大小已经确定的情况,都是精确尺寸。
		 * MeasureSpec.AT_MOST是最大尺寸,当控件的layout_width或layout_height指定为WRAP_CONTENT时,控件大小一般随着控件的子空间或内容进行变化,此时控件尺寸仅仅要不超过父控件同意的最大尺寸就可以。因此,此时的mode是AT_MOST,size给出了父控件同意的最大尺寸。
		 * MeasureSpec.UNSPECIFIED是未指定尺寸,这样的情况不多,一般都是父控件是AdapterView,通过measure方法传入的模式。
		 */
		mWidth = MeasureSpec.getSize(widthMeasureSpec);  //获取MyScrollView的宽度
		mHeight = MeasureSpec.getSize(heightMeasureSpec); //获取MyScrollView的高度
	}
	
	/**设置右滑的菜单View*/
	public void setMenu(View menu){
		mMenuView = menu;
		addView(mMenuView);
	}
	
	/**
	 * 设置主界面View
	 */
	public void setPrimary(View primary){
		mPriView = primary;
		addView(mPriView);
	}

}
第二步:按须要设置界面位置

	@Override
	protected void onLayout(boolean arg0, int arg1, int arg2, int arg3, int arg4) {
		mMenuView.layout(-(int)(mWidth * mMenuWeight), 0, 0, mHeight);
		mPriView.layout(0, 0, mWidth, mHeight);
	}

第三步:实现左右滑动

package com.example.testscrollto;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Scroller;

public class MyScrollView extends ViewGroup{
	
	private Context mContext;
	private int mWidth;
	private int mHeight;
	
	private float mMenuWeight = 3.0f / 5; //菜单界面比例
	
	private View mMenuView;   //菜单界面
	private View mPriView;	  //内容界面
	
	private boolean mIsShowMenu;
	
	private Scroller mScroller;
	
	
	public MyScrollView(Context context, AttributeSet attrs) {
		super(context, attrs);
		mContext = context;
		mScroller = new Scroller(mContext);
	}

	@Override
	protected void onLayout(boolean arg0, int arg1, int arg2, int arg3, int arg4) {
		mMenuView.layout(-(int)(mWidth * mMenuWeight), 0, 0, mHeight);
		mPriView.layout(0, 0, mWidth, mHeight);
	}
	
	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
		mWidth = MeasureSpec.getSize(widthMeasureSpec);  //获取MyScrollView的宽度
		mHeight = MeasureSpec.getSize(heightMeasureSpec); //获取MyScrollView的高度
	}
	
	/**设置右滑的菜单View*/
	public void setMenu(View menu){
		mMenuView = menu;
		addView(mMenuView);
	}
	
	/**
	 * 设置主界面View
	 */
	public void setPrimary(View primary){
		mPriView = primary;
		addView(mPriView);
	}
	
	private float mDownX;
	
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		float x = event.getX();
		switch (event.getAction()) {
		case MotionEvent.ACTION_DOWN:
			System.out.println("ACTION_DOWN");
			mDownX = x;          //记录按下时的x坐标
			break;
		case MotionEvent.ACTION_UP:
			System.out.println("ACTION_UP");
			int dis = (int) (x - mDownX);   //滑动的距离
			if(Math.abs(dis) > (mWidth * mMenuWeight / 2)){
				if(dis > 0){          //假设>0则是向右滑动
					showMenu();
				}else{				  //假设<0则是向左滑动
					hideMenu();
				}
			}
			break;
		default:
			break;
		}
		
		return true;
	}
	
	@Override
	public void computeScroll() {
		super.computeScroll();
		if(mScroller.computeScrollOffset()){
			scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
			postInvalidate();
		}
	}
	
	public boolean isShowMenu(){
		return mIsShowMenu;
	}
	
	public void showMenu(){
		 if(mIsShowMenu){
			 return;
		 }
		 mIsShowMenu = true;  //标记菜单已经显示
		 int dx = (int)(mWidth * mMenuWeight);  //滑动到目标位置的距离
		 mScroller.startScroll(getScrollX(), 0, -dx, 0, 500);
		 invalidate();
	}
	
	public void hideMenu(){
		if(!mIsShowMenu){
			return;
		}
		mIsShowMenu = false;
		int dx = (int)(mWidth * mMenuWeight);
		mScroller.startScroll(getScrollX(), 0, dx, 0, 500);
		invalidate();
	}
	 
		
}
从上面代码中能够看到以下两句代码触发computeScroll()方法

mScroller.startScroll(getScrollX(), 0, -dx, 0, 500);
invalidate();
第四步:加入窗体状态切换监听接口
	public interface OnMenuChangedListener{
		 public void onChanged(boolean isShow);
	}	
	
	public void setOnMenuChangedListener(OnMenuChangedListener listener){
		mListener = listener;
	}
将showMenu()方法和hideMenu()方法改动例如以下:

	public void showMenu(){
		 if(mIsShowMenu){
			 return;
		 }
		 mIsShowMenu = true;
		 int dx = (int)(mWidth * mMenuWeight);
		 mScroller.startScroll(getScrollX(), 0, -dx, 0, 500);
		 if(mListener != null){
			 mListener.onChanged(mIsShowMenu);
		 }
		 invalidate();
	}
	
	public void hideMenu(){
		if(!mIsShowMenu){
			return;
		}
		mIsShowMenu = false;
		int dx = (int)(mWidth * mMenuWeight);
		mScroller.startScroll(getScrollX(), 0, dx, 0, 500);
		 if(mListener != null){
			 mListener.onChanged(mIsShowMenu);
		 }
		 invalidate();
	}
在MainActivity中加入监听

		mScrollView.setOnMenuChangedListener(new OnMenuChangedListener() {
			
			@Override
			public void onChanged(boolean isShow) {
				System.out.println("窗体切换了一次");
			}
		});
第五步:依据详细业务及需求实现菜单列表及界面(直接贴出代码)

MainActivity.java

package com.example.testrefreshview;


import android.app.Activity;
import android.graphics.Color;
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 com.example.testrefreshview.RightScrollView.OnMenuChangedListener;

/**
 * 測试具有右滑菜单功能的ViewGroup,RigthScrollView
 *@author Lqh
 */
public class MainActivity extends Activity {

	private RightScrollView mRightScrollView;
	private Button mShowMenuBtn;
	private ListView mMenuList;
	private ArrayAdapter<String> mAdapter;
	private String[] menus = {"附近的人", "我的资料", "设置", "游戏", "即时聊天"};
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.rightscrollview_test);
		mRightScrollView = (RightScrollView)findViewById(R.id.rightscrollview);
		final View menu = getLayoutInflater().inflate(R.layout.rightscrollview_menu, null);
		final View primary = getLayoutInflater().inflate(R.layout.rightscrollview_primary, null);
		mMenuList = (ListView) menu.findViewById(R.id.list_right_menu);
		mShowMenuBtn = (Button) primary.findViewById(R.id.btn_showmenu);
		mAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, menus);
		mMenuList.setAdapter(mAdapter);
		
		mShowMenuBtn.setOnClickListener(new OnClickListener() {
			public void onClick(View v) {
				if(mRightScrollView.isShowMenu()){
					mRightScrollView.hideMenu();
				}else{
					mRightScrollView.showMenu();
				}
			}
		});
		
		mRightScrollView.setOnMenuChangedListener(new OnMenuChangedListener() {
			public void onChanged(boolean isShow) {
				if(isShow){
					mShowMenuBtn.setText("隐藏菜单");
				}else{
					mShowMenuBtn.setText("显示菜单");
				}
			}
		});
		
		mRightScrollView.setMenu(menu);
		mRightScrollView.setPrimary(primary);
		
		mMenuList.setOnItemClickListener(new OnItemClickListener() {
			public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
				switch(position){
				case 0:
					primary.setBackgroundColor(Color.CYAN);
					break;
				case 1:
					primary.setBackgroundColor(Color.BLUE);
					break;
				case 2:
					primary.setBackgroundColor(Color.GRAY);
					break;
				case 3:
					primary.setBackgroundColor(Color.MAGENTA);
					break;
				case 4:
					primary.setBackgroundColor(Color.YELLOW);
					break;
				}
				mRightScrollView.hideMenu();
			}
		});
		
	}
}
RightScrollView.java

package com.example.testrefreshview;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Scroller;

/**
 * 具有右滑菜单的ViewGroup,相似于Facebook的主界面
 * @author Lqh
 */
public class RightScrollView extends ViewGroup {

	private Context mContext;
	private Scroller mScroller;
	private View mMenuView;
	private View mPriView;
	private int mWidth;
	private int mHeight;
	private boolean mIsShowMenu;
	private float mMenuWeight = 3.0f / 5;
	private OnMenuChangedListener mListener;
	
	
	public RightScrollView(Context context) {
		this(context, null);
	}
	
	public RightScrollView(Context context, AttributeSet attrs) {
		super(context, attrs);
		
		mContext = context;
		mScroller = new Scroller(mContext);
		
	}

	/**设置右滑的菜单View*/
	public void setMenu(View menu){
		mMenuView = menu;
		addView(mMenuView);
	}
	
	/**
	 * 设置主界面View
	 */
	public void setPrimary(View primary){
		mPriView = primary;
		addView(mPriView);
	}
	
	public boolean isShowMenu(){
		return mIsShowMenu;
	}
	
	public void setOnMenuChangedListener(OnMenuChangedListener listener){
		mListener = listener;
	}
	
	
	public void showMenu(){
		 if(mIsShowMenu){
			 return;
		 }
		 mIsShowMenu = true;
		 int dx = (int)(mWidth * mMenuWeight);
		 mScroller.startScroll(getScrollX(), 0, -dx, 0, 500);
		 if(mListener != null){
			 mListener.onChanged(mIsShowMenu);
		 }
		 invalidate();
	}
	
	public void hideMenu(){
		if(!mIsShowMenu){
			return;
		}
		mIsShowMenu = false;
		int dx = (int)(mWidth * mMenuWeight);
		mScroller.startScroll(getScrollX(), 0, dx, 0, 500);
		 if(mListener != null){
			 mListener.onChanged(mIsShowMenu);
		 }
		 invalidate();
	}
	
	private float mDownX;
	
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		
		float x = event.getX();
		
		switch(event.getAction()){
		case MotionEvent.ACTION_DOWN:
			mDownX = x;
			break;
		case MotionEvent.ACTION_UP:
			int dis = (int) (x - mDownX);
			if(Math.abs(dis) > (mWidth * mMenuWeight / 2)){
				if(dis > 0){
					showMenu();
				}else{
					hideMenu();
				}
			}
			break;
		}
		
		return true;
	}
	
	
	 @Override
	public void computeScroll() {
		super.computeScroll();
		if(mScroller.computeScrollOffset()){
			scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
			postInvalidate();
		}
	}
	
	
	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		
		mWidth = MeasureSpec.getSize(widthMeasureSpec);
		mHeight = MeasureSpec.getSize(heightMeasureSpec);
		
		setMeasuredDimension(mWidth, mHeight);
		int widthSpec = MeasureSpec.makeMeasureSpec((int)(mWidth * mMenuWeight), MeasureSpec.EXACTLY);
		int heightSpec = MeasureSpec.makeMeasureSpec(mHeight, MeasureSpec.EXACTLY);
		mMenuView.measure(widthSpec, heightSpec);
		
		widthSpec = MeasureSpec.makeMeasureSpec(mWidth, MeasureSpec.EXACTLY);
		mPriView.measure(widthSpec, heightSpec);
	}
	
	@Override
	protected void onLayout(boolean changed, int l, int t, int r, int b) {
		
		mMenuView.layout(-(int)(mWidth * mMenuWeight), 0, 0, mHeight);
		mPriView.layout(0, 0, mWidth, mHeight);
	}

	 public interface OnMenuChangedListener{
		 public void onChanged(boolean isShow);
	 }
	
	 
}
运行效果:



源码下载:http://download.csdn.net/detail/lxq_xsyu/7231677