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

在上一篇文章《Android自己定义组件系列【3】——自己定义ViewGroup实现側滑》中实现了仿Facebook和人人网的側滑效果,这一篇我们将接着上一篇来实现双面滑动的效果。

1、布局示意图:


2、核心代码

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
		mWidth = MeasureSpec.getSize(widthMeasureSpec);  //获取MyScrollView的宽度
		mHeight = MeasureSpec.getSize(heightMeasureSpec); //获取MyScrollView的高度
		if(!isLocked){
			initX = getScrollX();
			isLocked = true;
		}
	}

在该方法中获取到初始的视图坐标偏移量getScrollX()

	@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则是向右滑动
					toRightMove();
				}else{				  //假设<0则是向左滑动
					toLeftMove();
				}
			}
			break;
		default:
			break;
		}

		return true;
	}

监听函数记录下按下和移动的屏幕坐标。求差计算出移动距离,假设这个距离大于阀值 (mWidth * mMenuWeight / 2)则滑动

	public void toRightMove(){
		 System.out.println("maxRight = " + maxRight);
		 System.out.println("X = "  + getScrollX());
		 if(getScrollX() >= initX){
			 int dx = (int)(mWidth * mMenuWeight);
			 mScroller.startScroll(getScrollX(), 0, -dx, 0, 500);
			 if(mListener != null){
				 mListener.onChanged();
			 }
			 invalidate();
		 }
	}

假设是向右滑动则。假设当前是初始位置(centerView在中间)则能够向右滑动(getScrollX == initX),或者当前左边View能够看见,则能够向右滑动将centerView移动到中间(getScrollX > initX).同理有向左滑动的方法。

	public void toLeftMove(){
		System.out.println("maxLeft = " + maxLeft);
		 System.out.println("X = "  + getScrollX());
		if(getScrollX() <= initX){
			int dx = (int)(mWidth * mMenuWeight);
			mScroller.startScroll(getScrollX(), 0, dx, 0, 500);
			if(mListener != null){
				mListener.onChanged();
			}
			invalidate();
		}
	}

3、所有代码

MyScrollView.java

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.LinearLayout;
import android.widget.Scroller;

public class MyScrollView extends LinearLayout{

	private Context mContext;
	private int mWidth;
	private int mHeight;

	private float mMenuWeight = 3.0f / 5; //菜单界面比例

	private View mMenuView;   //菜单界面
	private View mPriView;	  //内容界面
	private View mRightView;  //右边界面

	private int maxLeft;
	private int maxRight;
	private int initX;

	private boolean isLocked = false;

	private Scroller mScroller;

	private OnMenuChangedListener mListener;

	public MyScrollView(Context context, AttributeSet attrs) {
		super(context, attrs);
		mContext = context;
		mScroller = new Scroller(mContext);

	}

	@Override
	protected void onLayout(boolean changed, int l, int t, int r, int b) {
		super.onLayout(changed, l, t, r, b);
		mMenuView.layout(-(int)(mWidth * mMenuWeight), 0, 0, mHeight);
		mPriView.layout(0, 0, mWidth, mHeight);
		mRightView.layout(mWidth, 0, mWidth + (int)(mWidth * mMenuWeight), mHeight);
	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
		mWidth = MeasureSpec.getSize(widthMeasureSpec);  //获取MyScrollView的宽度
		mHeight = MeasureSpec.getSize(heightMeasureSpec); //获取MyScrollView的高度
		if(!isLocked){
			initX = getScrollX();
			isLocked = true;
		}
	}

	/**设置右滑的菜单View*/
	public void setMenu(View menu){
		mMenuView = menu;
		addView(mMenuView);
	}

	/**
	 * 设置主界面View
	 */
	public void setPrimary(View primary){
		mPriView = primary;
		addView(mPriView);
	}

	public void setRightView(View rightview){
		mRightView = rightview;
		addView(mRightView);
	}

	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则是向右滑动
					toRightMove();
				}else{				  //假设<0则是向左滑动
					toLeftMove();
				}
			}
			break;
		default:
			break;
		}

		return true;
	}

	@Override
	public void computeScroll() {
		super.computeScroll();
		if(mScroller.computeScrollOffset()){
			scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
			postInvalidate();
		}
	}

	public void toRightMove(){
		 System.out.println("maxRight = " + maxRight);
		 System.out.println("X = "  + getScrollX());
		 if(getScrollX() >= initX){
			 int dx = (int)(mWidth * mMenuWeight);
			 mScroller.startScroll(getScrollX(), 0, -dx, 0, 500);
			 if(mListener != null){
				 mListener.onChanged();
			 }
			 invalidate();
		 }
	}

	public void toLeftMove(){
		System.out.println("maxLeft = " + maxLeft);
		 System.out.println("X = "  + getScrollX());
		if(getScrollX() <= initX){
			int dx = (int)(mWidth * mMenuWeight);
			mScroller.startScroll(getScrollX(), 0, dx, 0, 500);
			if(mListener != null){
				mListener.onChanged();
			}
			invalidate();
		}
	}

	public interface OnMenuChangedListener{
		 public void onChanged();
	}	

	public void setOnMenuChangedListener(OnMenuChangedListener listener){
		mListener = listener;
	}
}

MainActivity.java

package com.example.testscrollto;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;

import com.example.testscrollto.MyScrollView.OnMenuChangedListener;

public class MainActivity extends Activity {

	private MyScrollView mScrollView;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		mScrollView = (MyScrollView)findViewById(R.id.rightscrollview);
		final View menu = getLayoutInflater().inflate(R.layout.rightscrollview_menu, null);
		final View primary = getLayoutInflater().inflate(R.layout.rightscrollview_primary, null);
		final View rightview = getLayoutInflater().inflate(R.layout.rightscrollview_right_menu, null);
		mScrollView.setMenu(menu);
		mScrollView.setPrimary(primary);
		mScrollView.setRightView(rightview);
		mScrollView.setOnMenuChangedListener(new OnMenuChangedListener() {

			@Override
			public void onChanged() {
				System.out.println("窗体切换了一次");
			}
		});
	}
}

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

    <com.example.testscrollto.MyScrollView
        android:id="@+id/rightscrollview"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

其余三个视图界面无限制。能够自由定义,这里就不贴出来了。

4、执行效果:


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

这样就能够实现左右滑动了,没有不论什么bug吗?

事实上这样看似是没有什么问题了,上面用于推断界面位置的代码在逻辑上看似是对的,可是在实际的应用中偶尔会出现错乱。

以下我们用第二种方式解决:

package com.example.jaohangui.view;

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

public class MyScrollLeftRightView extends LinearLayout{

	private Scroller mScroller;

	private View mLeftView;  //坐标界面
	private View mMainView;  //中间主界面
	private View mRightView; //右边界面

	private float mMeasureWight = 3.0f / 5; //菜单界面比例
	private int mWidth;
	private int mHeight;

	private boolean isLocked = false;
	private boolean isToLeft = false;
	private static int CENTER_PAGE = 1;
	private static int LEFT_PAGE = 0;
	private static int RIGHT_PAGE = 2;
	private int currentPage = CENTER_PAGE;

	public MyScrollLeftRightView(Context context, AttributeSet attrs) {
		super(context, attrs);
		mScroller = new Scroller(context);
	}

	@Override
	protected void onLayout(boolean changed, int l, int t, int r, int b) {
		super.onLayout(changed, l, t, r, b);
		mLeftView.layout(-(int)(mWidth * mMeasureWight), 0, 0, mHeight);
		mMainView.layout(0, 0, mWidth, mHeight);
		mRightView.layout(mWidth, 0, mWidth + (int)(mWidth * mMeasureWight), mHeight);
	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
		mWidth = MeasureSpec.getSize(widthMeasureSpec);
		mHeight = MeasureSpec.getSize(heightMeasureSpec);
	}

	/**
	 * 加入左边界面内容
	 * @param view
	 */
	public void setLeftView(View view){
		mLeftView = view;
		addView(mLeftView);
	}

	/**
	 * 加入主界面内容
	 * @param view
	 */
	public void setMainView(View view){
		mMainView = view;
		addView(mMainView);
	}

	/**
	 * 加入右边界面内容
	 * @param view
	 */
	public void setRightView(View view){
		mRightView = view;
		addView(mRightView);
	}

	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 * mMeasureWight / 3)){
				if(dis > 0){
					toRightMove();
				}else{
					toLeftMove();
				}
			}
			break;

		default:
			break;
		}
		return true;
	}

	@Override
	public void computeScroll() {
		super.computeScroll();
		if(mScroller.computeScrollOffset()){
			isLocked = true;
			scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
			postInvalidate();
		}else{
			if(currentPage == CENTER_PAGE){
				if(isToLeft){
					currentPage = RIGHT_PAGE;
				}else{
					currentPage = LEFT_PAGE;
				}
			}else{
				currentPage = CENTER_PAGE;
			}
			isLocked = false;
		}
	}

	public void toRightMove(){
		if(currentPage == LEFT_PAGE || isLocked){
			return;
		}
		int dx = (int)(mWidth * mMeasureWight);
		mScroller.startScroll(getScrollX(), 0, -dx, 0, 500);
		invalidate();
		isToLeft = false;
	}

	public void toLeftMove(){
		if(currentPage == RIGHT_PAGE || isLocked){
			return;
		}
		System.out.println("ok");
		int dx = (int)(mWidth * mMeasureWight);
		mScroller.startScroll(getScrollX(), 0, dx, 0, 500);
		invalidate();
		isToLeft = true;
	}
}

上面使用了两个用来推断的变量和一个锁定状态(不让进入toLeftMove和toRightMove)的变量。

1、在进入toLeftMove或者toRightMove方法的时候首先会推断是否isLocked为true,假设为true则说明当前是正在滑动状态,不能够执行这两个方法。假设不这样去控制。在界面正在滑动的时候上面的currentPage就会发生错乱。

2、将要滑动之前告诉computeScroll()方法,是从toLeftMove和toRightMove的那个方法中出来的(使用isToLeft)。

3、最后推断Scroller是否已经停止滑动(移动),假设停止则改变当前页面的状态(currentPage的值)

有的时候我们尽管实现了一个功能或者某个逻辑,而怎样才干使这段代码更加健壮和有效更值得我们去认真思考。

。。

时间: 2024-10-05 12:43:31

Android自己定义组件系列【4】——自己定义ViewGroup实现双側滑动的相关文章

Android自己定义组件系列【6】——进阶实践(3)

上一篇<Android自己定义组件系列[5]--进阶实践(2)>继续对任老师的<可下拉的PinnedHeaderExpandableListView的实现>进行了分析,这一篇计划中间插一段"知识点",对Android中的事件分发机制进行解析.细心的朋友可能会发现,打开大牛写的Android项目,里面非常多组件都是自己定义的(这就是为什么界面和体验这么吸引你的原因),可是要灵活的去自己定义组件就必须对手势(也就是各种监听)必须熟悉,能处理好事件之间的关系. 先看一

Android自己定义组件系列【5】——进阶实践(2)

上一篇<Android自己定义组件系列[5]--进阶实践(1)>中对任老师的<可下拉的PinnedHeaderExpandableListView的实现>前一部分进行了实现,这一篇我们来看看ExpandableListView的使用并实现剩下的部分. 原文出处:http://blog.csdn.net/singwhatiwanna/article/details/25546871 一.ExpandableListView的使用方法 ExpandableListView是ListVi

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

有关自己定义ViewGroup的文章已经非常多了,我为什么写这篇文章,对于刚開始学习的人或者对自己定义组件比較生疏的朋友尽管能够拿来主义的用了,可是要一步一步的实现和了解当中的过程和原理才干真真脱离别人的代码,举一反三却不easy,非常多博主事实上不愿意一步一步的去写,这样非常耗时,可是假设能对读者有帮助,能从这篇文章中学会自己定义组件就达到我的目的了. 第一步:搭建框架来实现一个3/5和2/5分屏的界面,效果例如以下: 最外层是一个自己定义的ViewGroup布局文件例如以下: package

Android自己定义组件系列【1】——自己定义View及ViewGroup

View类是ViewGroup的父类,ViewGroup具有View的全部特性.ViewGroup主要用来充当View的容器.将当中的View作为自己孩子,并对其进行管理.当然孩子也能够是ViewGroup类型. View类一般用于画图操作,重写它的onDraw方法,但它不能够包括其它组件,没有addView(View view)方法. ViewGroup是一个组件容器,它能够包括不论什么组件,但必须重写onLayout(boolean changed,int l,int t,int r,int

Android自己定义组件系列【8】——面膜文字动画

我们掩盖文字动画Flash中非经货共同体共同,由于Android应用程序开发人员做你想要做这个动画在应用程序中去?本文中,我们看的是如何自己的定义ImageView来实现让一张文字图片实现文字的遮罩闪烁效果,以下先来看看效果吧. (录屏幕延时导致效果看起来不是非常好) 一.实现原理 实现原理是重写View的onCreate方法.获取图片资源后对每一个像素的透明度进行改动来实现,再启动一个线程来循环改变某个区域中的像素透明度. RGBA基础知识:(以下几段介绍文字引用自维基百科) RGBA是代表R

Android自己定义组件系列【5】——高级实践(1)

在接下来的几篇文章将任老师的博文<您可以下拉PinnedHeaderExpandableListView实现>骤来具体实现.来学习一下大神的代码并记录一下. 原文出处:http://blog.csdn.net/singwhatiwanna/article/details/25546871 先看一下终于效果: watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc2luZ3doYXRpd2FubmE=/font/5a6L5L2T/fontsize/400/f

Android自己定义组件系列【2】——Scroller类

在上一篇中介绍了View类的scrollTo和scrollBy两个方法,对这两个方法不太了解的朋友能够先看<自己定义View及ViewGroup> scrollTo和scrollBy尽管实现了视图的偏移,可是却没有更好的控制移动过程,移动是瞬间进行的.Scroller类就是为解决问题而设计的. 打开Scroller的源码,能够看到startScroll方法: /** * Start scrolling by providing a starting point and the distance

Android画图系列(二)——自己定义View绘制基本图形

这个系列主要是介绍下Android自己定义View和Android画图机制.自己能力有限.假设在介绍过程中有什么错误.欢迎指正 前言 在上一篇Android画图系列(一)--自己定义View基础中我们了解自己定义View相关的基本知识.只是,这些东西依然还是理论,接下来我们就实际绘制一些东西 在本篇文章中,我们先了解下面Canvas,而且画一些主要的图形 Canvas简单介绍 Canvas我们能够称之为画布.能够在上面绘制各种东西.是安卓平台2D图形绘制的基础.非常强大. 一般来说,比較基础的东

Android自定义组件系列【7】——进阶实践(4)

上一篇<>中补充了关于Android中事件分发的过程知识,这一篇我们接着来分析任老师的<可下拉的PinnedHeaderExpandableListView的实现>. 一.StickyLayout中的OnGiveUpTouchEventListener接口的作用是什么? public interface OnGiveUpTouchEventListener { public boolean giveUpTouchEvent(MotionEvent event); } 在Sticky