自定义ViewGroup

通过自定义ViewGroup我们能够实现很强大的页面切换效果,下面是我的一点分享

要自定义ViewGroup,就要先继承ViewGroup,然后实现其onLayout(),onMeasure(),onTouchEvent()方法,

其中onlayout()方法是用来定义子控件的布局
通过view.layout(getWidth()*i,0,getWidth()*(i+1),getHeight());方法依次将子view的布局大小添加到组合控件中去

onMeasure()方法用来测量子view的大小
通过view.measure(widthMeasureSpec, heightMeasureSpec);方法,依次计算子view的大小

onTouchEvent()响应点击事件和GestureDetector来协同判断页面的切换方式

此外通过onInterceptTouchEvent()来进行事件中断,解决子view和父view事件冲突的问题

具体实现代码如下:
public class MyViewGroup extends ViewGroup{
	private Context context;
	/**
	 * 定义手势识别工具类
	 */
	private GestureDetector gesture;
	//系统的计算位移工具类
	private Scroller scroller;
	
	/**
	 * 是否发生快速滑动
	 */
	private boolean isFling;
	public MyViewGroup(Context context, AttributeSet attrs) {
		super(context, attrs);
		this.context = context;
		initView();
	}

	private void initView() {
		//scroller = new MyScroller(context);
		scroller = new Scroller(context);
		gesture = new GestureDetector(context, new OnGestureListener() {
			
			/**
			 * 当有一个焦点抬起的时候调用
			 */
			@Override
			public boolean onSingleTapUp(MotionEvent e) {
				// TODO Auto-generated method stub
				return false;
			}
			
			/**
			 * 当手指按下的时候调用
			 */
			@Override
			public void onShowPress(MotionEvent e) {
				// TODO Auto-generated method stub
				
			}
			
			/**
			 * 当手指滑动的时候调用
			 */
			@Override
			public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
					float distanceY) {
				/**
				 * 移动当前视图的内容
				 * disx  x方向上移动的距离  为正时向左移动,为负时向右移动
				 * disy  y方向上移动的距离
				 */
				scrollBy((int) distanceX, 0);
				return false;
			}
			
			/**
			 * 当手指长按的时候调用
			 */
			@Override
			public void onLongPress(MotionEvent e) {
				// TODO Auto-generated method stub
				
			}
			
			/**
			 * 当手指快速滑动的时候调用
			 */
			@Override
			public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
					float velocityY) {
				isFling = true;
				if(velocityX>0&¤tId>0){//快速向右滑动
					currentId--;
				}else if(velocityX<0&¤tId<getChildCount()-1){//快速向左滑动
					currentId++;
				}
				moveToCurrentView(currentId);
				return false;
			}
			
			@Override
			public boolean onDown(MotionEvent e) {
				return false;
			}
		});
	}

	/**
	 * 对view进行布局,确定子view的位置
	 * changed 为true时
	 * l\t\r\b指定子view在父view中的位置
	 */
	@Override
	protected void onLayout(boolean changed, int l, int t, int r, int b) {
		// TODO Auto-generated method stub
		for(int i = 0;i<getChildCount();i++){
			//父View 会根据子view的大小和自身情况,综合确定子view的位置和大小
			View view = getChildAt(i);
			//指定子view的位置,是指在父view中的位置,左,上,右,下
			view.layout(getWidth()*i,0,getWidth()*(i+1),getHeight());
		}
	}
	/**
	 * 计算控件大小,还有一个任务就是计算子View 的大小,如果不计算就不会显示子view的视图
	 */
	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		// TODO Auto-generated method stub
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
		for(int i=0;i<getChildCount();i++){
			
			View view = getChildAt(i);
			//计算子控件的大小
			view.measure(widthMeasureSpec, heightMeasureSpec);
			
		}
	}
	/**
	 * 记录当前屏幕显示的子view下标
	 */
	private int currentId = 0;
	/**
	 * 记录鼠标点击下时的x坐标
	 */
	private int firstX;
	/**
	 * 记录鼠标点击下时的x坐标
	 */
	private int firstY;
	
	/**
	 * 中断点击事件,如果为true就会中断事件,执行自己的OnTouch事件
	 * 为false不中断,交给子view执行
	 */
	@Override
	public boolean onInterceptTouchEvent(MotionEvent ev) {
		boolean result = false;
		switch(ev.getAction()){
		case MotionEvent.ACTION_DOWN:
			//点击时也要为手势注册监听,否则会接收不到点下事件
			gesture.onTouchEvent(ev);
			firstX = (int) ev.getX();
			firstY = (int) ev.getY();
			break;
		case MotionEvent.ACTION_MOVE:
			int dx = (int) (Math.abs(ev.getX()-firstX));
			int dy = (int) (Math.abs(ev.getY()-firstY));
			if(dx>dy&& dx>10){
				result = true;
			}
			break;
		case MotionEvent.ACTION_UP:
			break;
		}
		return result;
	}
	
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		gesture.onTouchEvent(event);
		switch(event.getAction()){
		case MotionEvent.ACTION_DOWN:
			firstX = (int) event.getX();
			break;
		case MotionEvent.ACTION_UP:
			if(!isFling){//没有执行快速滑动的时候才执行按位置滑行
				int nextId =0;
				if((firstX - event.getX())>getWidth()/2){
					nextId = currentId + 1;
				}else if((event.getX()-firstX)>getWidth()/2){
					nextId = currentId - 1;
				}else{
					nextId = currentId;
				}
				moveToCurrentView(nextId);
			}
			isFling = false;
			break;
		case MotionEvent.ACTION_MOVE:
			
			break;
		}
		return true;
	}

	/**
	 * 移动到指定的位置上
	 * @param nextId
	 */
	public void moveToCurrentView(int nextId) {
		//对nextId进行检验,避免出错
		currentId = nextId>=0 ? nextId:0;
		currentId = nextId>=(getChildCount()-1) ?  (getChildCount()-1):nextId;
		//瞬间移动
		//scrollTo(currentId*getWidth(), 0);
		if(mypagechangeListener!=null){
			mypagechangeListener.movetoCurrentpage(currentId);
		}
		int distance = currentId*getWidth() - getScrollX(); 
		
		/**
		 * 开始移动
		 * startX  开始时的x轴坐标
		 * startY  开始时的y轴坐标
		 * dx      x轴滑动的距离
		 * dy      y轴滑动的距离
		 * duration  运行时间,如果不指定则为默认
		 */
		scroller.startScroll(getScrollX(),0,distance,0);
		//刷新界面
		invalidate();
	}
	
	/**
	 * invalidate()会导致computeScroll()的执行
	 */
	@Override
	public void computeScroll() {
		//如果还在滑动
		if(scroller.computeScrollOffset()){
			int newx = (int) scroller.getCurrX();
			scrollTo(newx, 0);
			invalidate();
		}
	}
	
	public MyPageChangeListener mypagechangeListener;
	public MyPageChangeListener getMypagechangeListener() {
		return mypagechangeListener;
	}

	public void setMypagechangeListener(MyPageChangeListener mypagechangeListener) {
		this.mypagechangeListener = mypagechangeListener;
	}
	/**
	 * 页面改变的监听事件
	 *
	 */
	public interface MyPageChangeListener{
		public void movetoCurrentpage(int currentId);
	}
}

写好了自定义viewGroup之后就是使用了:
先来看布局文件:
<?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:id="@+id/ll_titles"
        android:layout_height="wrap_content"
        android:layout_width="match_parent"
        android:orientation="horizontal">
    </LinearLayout>
    <com.test.MyViewGroup
        android:id="@+id/mvg_show"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

</LinearLayout>

接下来是主程序文件:

public class MyViewGroupActivity extends Activity {
	private String[] titles = new String[]{"菜单1","菜单2","菜单3","菜单4","菜单5","菜单6"};
	private MyViewGroup mvg_show;
	private LinearLayout ll_titles;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		// TODO Auto-generated method stub
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_myviewgroup);
		mvg_show = (MyViewGroup) findViewById(R.id.mvg_show);
		ll_titles = (LinearLayout) findViewById(R.id.ll_titles);
		for(int i =0 ;i <titles.length;i++){
			View view = getLayoutInflater().inflate(R.layout.activity_test,null);
			view.setBackgroundColor(Color.rgb(0, i*20+40, 0));
			mvg_show.addView(view,i);
			TextView tv = new TextView(this);
			LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT);
			params.setMargins(20, 5, 20, 5);
			tv.setLayoutParams(params);
			tv.setText(titles[i]);
			tv.setId(i);
			tv.setClickable(true);
			tv.setFocusable(true);
			if(i==0){
				tv.setTextColor(Color.RED);
			}else{
				tv.setTextColor(Color.BLACK);
			}
			ll_titles.addView(tv);
		}
		//滑动页面显示对应的标题
		mvg_show.setMypagechangeListener(new MyPageChangeListener(){

			@Override
			public void movetoCurrentpage(int currentId) {
				for(int i =0 ;i <titles.length;i++){
					if(i!=currentId){
						((TextView)ll_titles.getChildAt(i)).setTextColor(Color.BLACK);
					}
				}
				((TextView)ll_titles.getChildAt(currentId)).setTextColor(Color.RED);
			}
		});
		//点击标题显示对应的页面
		for(int i =0 ;i <titles.length;i++){
			((TextView)ll_titles.getChildAt(i)).setOnClickListener(new OnClickListener(){
				@Override
				public void onClick(View view) {
					((TextView)ll_titles.getChildAt(view.getId())).setTextColor(Color.RED);
					mvg_show.moveToCurrentView(view.getId());
				}
			});
		}
	}
}

最终的效果如下:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值