自定义实现类似android主界面的滑屏换屏控件

今天讲一下我开发“微读”阅读软件中实现的一个类似android主界面的滑屏控件,这个滑屏在微读的主界面和看书时左右滑平翻页中都有用到,在具体的应用效果如下:
下载到手机中体验一下

微读软件:点击下载

网站:http://www.app1001.com/ 

新浪微博:@easy微读

直接效果图 ,更多内容可以参看:我的android阅读软件“微读”-做最简单的手机阅读软件

    

       实现思路,刚开始的时候我是用ViewFlipper控件来做非常的简单但是实现不了拖拽移动屏幕的效果,最终放弃决定自定义一个控件实现这样效果。

接下来我详细的解说一下我开发时写的这个实验demo,软件中用的滑屏就是由这样的代码实现的。

       首先新建一个控件类TouchPageView并且继承自ViewGroup,左右滑动换屏我的实现是在TouchPageView添加3个子view分别代表看不到的左边屏幕、可以看到的中间屏幕、看不到的右边屏幕,这样在滑屏时候就可以通过不断调整这3个view的位置实现连续不间断滑屏换屏,下面的实验中我分别把3个view设置成红色、绿色、黄色这样切换的时候可以看到明显效果,这3个view在TouchPageView的构造方法中调用init方法进行初始化:

private void init()
{
views= new ArrayList<LinearLayout>();
view1=new LinearLayout(context);
view1.setBackgroundColor(Color.YELLOW);
this.addView(view1);
TextView tv=new TextView(context);
tv.setText("测试");
view1.addView(tv);
views.add(view1);


view2=new LinearLayout(context);
view2.setBackgroundColor(Color.RED);
this.addView(view2);
views.add(view2);

view3=new LinearLayout(context);
view3.setBackgroundColor(Color.GREEN);
this.addView(view3);
views.add(view3);

final ViewConfiguration configuration = ViewConfiguration.get(getContext());
mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
}

       接下来的实现是关键,重写onLayout方法对3个view的显示位置布局进行控制,通过下面的这个方法,把3个view进行水平一个跟着一个进行布局显示。

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int childLeft = -1;
final int count = views.size();
//水平从左到右放置
for (int i = 0; i < count; i++) {

final View child =views.get(i);
if (child.getVisibility() != View.GONE) {
final int childWidth = child.getMeasuredWidth();
if(childLeft==-1)
{
childLeft=-childWidth;
}
child.layout(childLeft, 0, childLeft + childWidth, child.getMeasuredHeight());
childLeft += childWidth;
}
}

}

       3个view位置放置好之后,接下来的实现实现手指在屏幕拖拽滑动时让3个view跟着手指的位置进行变化显示,这个肯定是在onTouchEvent方法中实现了,分别在MotionEvent.ACTION_DOWN、MotionEvent.ACTION_MOVE、MotionEvent.ACTION_UP三个手指状态中进行控制,在下面的实现中还采用了VelocityTracker的方法对手指的滑动速度进行跟踪,这样根据滑动速度决定屏幕往哪个方向换屏,关键的代码如下:

@Override
public boolean onTouchEvent(MotionEvent ev){

if(!lock)
{
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(ev);

final int action = ev.getAction();
final float x = ev.getX();
final float y = ev.getY();

switch (action) {
case MotionEvent.ACTION_DOWN://按下去
if(touchState==TOUCH_STATE_REST)

{
//记录按下去的的x坐标
lastMotionX = x;

touchState=TOUCH_STATE_MOVING;

isMoved=false;
}

break;
case MotionEvent.ACTION_MOVE://拖动时
if(touchState==TOUCH_STATE_MOVING)

{
float offsetX=x-lastMotionX;
float offsetY=y-lastMotionY;

if(isMoved)
{
lastMotionX=x;
lastMotionY=y;

final int count = views.size();
//水平从左到右放置
for (int i = 0; i < count; i++) {

final View child =views.get(i);
if (child.getVisibility() != View.GONE) {
final int childWidth = child.getMeasuredWidth();
int childLeft = child.getLeft()+(int)offsetX;
child.layout(childLeft, 0, childLeft + childWidth, child.getMeasuredHeight());
childLeft += childWidth;
}
}
}
else if(Math.abs(offsetX)>TOUCH_SLOP||Math.abs(offsetY)>TOUCH_SLOP)
{
//移动超过阈值,则表示移动了
isMoved=true;

removeCallbacks(mLongPressRunnable);
}
}

break;
case MotionEvent.ACTION_UP://放开时
//释放了
removeCallbacks(mLongPressRunnable);


if(isMoved)
{
if(touchState==TOUCH_STATE_MOVING)
{
touchState=TOUCH_STATE_SLOWING;
int sign=0;
final VelocityTracker velocityTracker = mVelocityTracker;
//计算当前速度
velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);

//x方向的速度
int velocityX = (int) velocityTracker.getXVelocity();

if(velocityX > SNAP_VELOCITY)//足够的能力向左
{

sign=1;
Log.e("enough to move left", "true");
}
else if (velocityX < -SNAP_VELOCITY)//足够的能力向右
{

sign=-1;
Log.e("enough to move right", "right");
}
else
{
sign=0;
}
moveToFitView(sign);
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}

}
}


break;
}
}
return true;
}

       完成手指滑的功能后,最后在手指离开屏幕的时候,让3个view滑动到合适的位置,保证当前屏幕只能看到一个完整的view另外2个view不可见,并且在滑动的过程中为了达到比较自然的效果,采用减速滑动的实现,这里是用了Handler进行间隔的减速移动效果,这样滑动起来比较舒服,其实最好的效果应该加入阻尼效果,就是让view一定程度的冲过屏幕边界然后在回弹,经过几次这样的缓减至速度为零然后最终停止,这个可以由各位自己去实现,并不难写。

int offset=0;
private void moveToFitView(int sign)
{
boolean b=swapView(sign);
if(true)
{
View view1=views.get(1);
int left=view1.getLeft();
//int offset=0;
if(left!=0)

{
offset=-1*left;
}

moveView();
}
}

FlipAnimationHandler mAnimationHandler;
int ovv=40;
private void moveView()
{
final int count = views.size();

if(offset!=0)
{
int ov=0;
if(offset>0)
{
ov=ovv;
}
else
{
ov=-1*ovv;
}
ovv=ovv-3;
if(ovv<1)
{
ovv=3;
}
if(Math.abs(offset)<Math.abs(ov))
{
ov=offset;
offset=0;

}
else
{
offset=offset-ov;
}

//水平从左到右放置
for (int i = 0; i < count; i++) {

final View child =views.get(i);
final int childWidth = child.getMeasuredWidth();
int childLeft = child.getLeft()+ov;
child.layout(childLeft, 0, childLeft + childWidth, child.getMeasuredHeight());
childLeft += childWidth;
}

if(mAnimationHandler==null)
{
mAnimationHandler = new FlipAnimationHandler();
}
mAnimationHandler.sleep(1);
}
else
{
ovv=40;
touchState=TOUCH_STATE_REST;
}
}

class FlipAnimationHandler extends Handler {
@Override
public void handleMessage(Message msg) {
TouchPageView.this.moveView();
}

public void sleep(long millis) {
this.removeMessages(0);
sendMessageDelayed(obtainMessage(0), millis);
}
}

整个自定义控件核心的思路和代码就上面这些了,实现效果请参看我的微读效果。

完整的代码:

 

package xx.weidu;

import java.util.ArrayList;
import java.util.List;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;

public class TouchPageView extends ViewGroup{

	private LinearLayout view1;
	private LinearLayout view2;
	private LinearLayout view3;
	

	//速度跟踪
	private VelocityTracker mVelocityTracker;
	private int mMaximumVelocity;
	
	//手势临界速度,当速度超过这个时切换到下一屏
    private static final int SNAP_VELOCITY = 100;
    
    //停止状态
	private final static int TOUCH_STATE_REST = 0;
    //滚动状态
	private final static int TOUCH_STATE_MOVING = 1;
	//减速停止状态
	private final static int TOUCH_STATE_SLOWING = 2;
	
	//当前触摸状态
	private int touchState = TOUCH_STATE_REST;
    
	private boolean lock=false;
	
	private float lastMotionX;
    private float lastMotionY;
    
	private Context context;
	private List<LinearLayout> views;
	//是否移动了
	private boolean isMoved;
	//长按的runnable
    private Runnable mLongPressRunnable;
	//移动的阈值
	private static final int TOUCH_SLOP=10;
	
    public int width;
	
	public int height;
	
	public TouchPageView(Context context) {
		super(context);
		this.context=context;
		init();
	}
	
	private void init()
	{
		views= new ArrayList<LinearLayout>();
		view1=new LinearLayout(context);
		view1.setBackgroundColor(Color.YELLOW);
		this.addView(view1);
		TextView tv=new TextView(context);
		tv.setText("测试");
		view1.addView(tv);
		views.add(view1);
		
		
		view2=new LinearLayout(context);
		view2.setBackgroundColor(Color.RED);
		this.addView(view2);
		views.add(view2);
		
		view3=new LinearLayout(context);
		view3.setBackgroundColor(Color.GREEN);
		this.addView(view3);
		views.add(view3);
		
		final ViewConfiguration configuration = ViewConfiguration.get(getContext());
		mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
	}
	
	
	@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
    	super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        final int count = views.size();
        for (int i = 0; i < count; i++) {
            final View child =views.get(i);
            child.measure(widthMeasureSpec,heightMeasureSpec);
        }
        
        int finalWidth, finalHeight;
		finalWidth = measureWidth(widthMeasureSpec);
		finalHeight = measureHeight(heightMeasureSpec);

        this.width=finalWidth;
		this.height=finalHeight;

	}
	
	private int measureWidth(int measureSpec) {
		int result = 0;
		int specMode = MeasureSpec.getMode(measureSpec);
		int specSize = MeasureSpec.getSize(measureSpec);

		if (specMode == MeasureSpec.EXACTLY) {
			result = specSize;
		} else {
			result = specSize;
		}

		return result;
	}
	
	private int measureHeight(int measureSpec) {
		int result = 0;
		int specMode = MeasureSpec.getMode(measureSpec);
		int specSize = MeasureSpec.getSize(measureSpec);

		if (specMode == MeasureSpec.EXACTLY) {
			result = specSize;
		} else {
			result = specSize;
		}
		return result;
	}

	@Override
	protected void onLayout(boolean changed, int l, int t, int r, int b) {
		int childLeft = -1;
        final int count = views.size();
        //水平从左到右放置
        for (int i = 0; i < count; i++) {
            final View child =views.get(i);
            if (child.getVisibility() != View.GONE) {
                final int childWidth = child.getMeasuredWidth();
                if(childLeft==-1)
                {
                	childLeft=-childWidth;
                }
                child.layout(childLeft, 0, childLeft + childWidth, child.getMeasuredHeight());
                childLeft += childWidth;
            }
        }
		
	}
	
	//绘制子元素
    @Override
	protected void onDraw(Canvas canvas) {
    	//水平从左到右放置
    	int count = views.size();
        for (int i = 0; i < count; i++) {
            View child =views.get(i);
            drawChild(canvas, child, getDrawingTime());
        }
    }

	@Override
    public boolean onTouchEvent(MotionEvent ev){
		
		if(!lock)
		{
			if (mVelocityTracker == null) {
	            mVelocityTracker = VelocityTracker.obtain();
	        }
	        mVelocityTracker.addMovement(ev);
	        
			final int action = ev.getAction();
	        final float x = ev.getX();
	        final float y = ev.getY();
	        
	        switch (action) {
	        case MotionEvent.ACTION_DOWN://按下去
	        	if(touchState==TOUCH_STATE_REST)
	        	{
	        		//记录按下去的的x坐标
	                lastMotionX = x;
	                touchState=TOUCH_STATE_MOVING;
	                
	                isMoved=false;
	        	}
	        	
	        	break;
	        case MotionEvent.ACTION_MOVE://拖动时
	        	if(touchState==TOUCH_STATE_MOVING)
	        	{
	        		float offsetX=x-lastMotionX;
	        		float offsetY=y-lastMotionY;
	        		
	        		if(isMoved)
	        		{
	        			lastMotionX=x;
		            	lastMotionY=y;

		            	final int count = views.size();
		                //水平从左到右放置
		                for (int i = 0; i < count; i++) {
		                    final View child =views.get(i);
		                    if (child.getVisibility() != View.GONE) {
		                        final int childWidth = child.getMeasuredWidth();
		                        int childLeft = child.getLeft()+(int)offsetX;
		                        child.layout(childLeft, 0, childLeft + childWidth, child.getMeasuredHeight());
		                        childLeft += childWidth;
		                    }
		                }
	        		}
	        		else if(Math.abs(offsetX)>TOUCH_SLOP||Math.abs(offsetY)>TOUCH_SLOP)
	        		{
	        			//移动超过阈值,则表示移动了
						isMoved=true;
						removeCallbacks(mLongPressRunnable);
	        		}
	        	}
	        	
	        	break;
	        case MotionEvent.ACTION_UP://放开时
	        	//释放了
				removeCallbacks(mLongPressRunnable);
				
				if(isMoved)
				{
					if(touchState==TOUCH_STATE_MOVING)
		        	{
		        		touchState=TOUCH_STATE_SLOWING;
		        		int sign=0;
		            	final VelocityTracker velocityTracker = mVelocityTracker;
		                //计算当前速度
		                velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
		                //x方向的速度
		                int velocityX = (int) velocityTracker.getXVelocity();
		                if(velocityX > SNAP_VELOCITY)//足够的能力向左
		                {
		                	sign=1;
		                	Log.e("enough to move left", "true");
		                }
		                else if (velocityX < -SNAP_VELOCITY)//足够的能力向右
		                {
		                	sign=-1;
		                	Log.e("enough to move right", "right");
		                }
		                else
		                {
		                	sign=0;
		                }
		            	moveToFitView(sign);
		            	if (mVelocityTracker != null) {
		                    mVelocityTracker.recycle();
		                    mVelocityTracker = null;
		                }
		            	
		        	}
				}
	        	
	        	
	        	break;
	        }
		}
		return true;
	}
	
	int offset=0;
	private void moveToFitView(int sign)
	{
		boolean b=swapView(sign);
		if(true)
		{
			View view1=views.get(1);
			int left=view1.getLeft();
			//int offset=0;
			if(left!=0)
			{
				offset=-1*left;
			}
			
			moveView();
		}
	}
	
	FlipAnimationHandler mAnimationHandler;
	int ovv=40;
	private void moveView()
	{
		final int count = views.size();
		
		if(offset!=0)
		{
			int ov=0;
			if(offset>0)
			{
			    ov=ovv; 
			}
			else
			{
				ov=-1*ovv;
			}
			ovv=ovv-3;
			if(ovv<1)
		    {
		    	ovv=3;
		    }
			if(Math.abs(offset)<Math.abs(ov))
			{
				ov=offset;
				offset=0;
				
			}
			else
			{
				offset=offset-ov;
			}
			
			//水平从左到右放置
			for (int i = 0; i < count; i++) {
				final View child =views.get(i);
				final int childWidth = child.getMeasuredWidth();
				int childLeft = child.getLeft()+ov;
				child.layout(childLeft, 0, childLeft + childWidth, child.getMeasuredHeight());
				childLeft += childWidth;
			}
			
			if(mAnimationHandler==null)
			{
				mAnimationHandler = new FlipAnimationHandler();
			}
			mAnimationHandler.sleep(1);
		}
		else
		{
			ovv=40;
			touchState=TOUCH_STATE_REST;
		}
	}
	
	class FlipAnimationHandler extends Handler {
		@Override
		public void handleMessage(Message msg) {
			TouchPageView.this.moveView();
		}

		public void sleep(long millis) {
			this.removeMessages(0);
			sendMessageDelayed(obtainMessage(0), millis);
		}
	}
	
	private boolean swapView(int sign)
	{
		boolean b=false;
		if(sign==-1)//向左
    	{
    		View view0=views.get(0);
    		if(view0.getLeft()<=-1*view0.getMeasuredWidth())
    		{
    			swapViewIndex(sign);
    			
    			View view2=views.get(1);
    			View view3=views.get(2);
    			int childWidth=view2.getMeasuredWidth();
    			int childLeft=view2.getLeft()+childWidth;
    			view3.layout(childLeft, 0, childLeft + view3.getMeasuredWidth(), view3.getMeasuredHeight());
    			b=true;
    		}
    	}
    	else if(sign==1)//向右
    	{
    		View view3=views.get(2);
    		if(view3.getLeft()>view3.getMeasuredWidth())
    		{
    			swapViewIndex(sign);
    			
    			View view1=views.get(0);
    			View view2=views.get(1);
    			int childRight=view2.getLeft();
    			int childLeft=childRight-view1.getMeasuredWidth();
    			view1.layout(childLeft, 0, childRight, view1.getMeasuredHeight());
    			b=true;
    		}
    	}
		
		return b;
	}
	
	private void swapViewIndex(int sign)
	{
		if(sign==-1)//向左
    	{
			LinearLayout v=views.remove(0);
			views.add(v);
    	}
    	else if(sign==1)//向右
    	{
    		LinearLayout v=views.remove(views.size()-1);
    		views.add(0, v);
    	}
	}
}

  

posted @ 2011-11-28 21:07  水的右边  阅读(10214)  评论(13编辑  收藏  举报