zoukankan      html  css  js  c++  java
  • 自定义实现类似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);
        	}
    	}
    }
    

      

  • 相关阅读:
    Android 程序架构: MVC、MVP、MVVM、Unidirectional、Clean...
    asp.net发布到IIS中出现错误:处理程序“PageHandlerFactory-Integrated”在其模块列表中有一个错误模块“ManagedPipelineHandler”
    反射中使用 BindingFlags.IgnoreCase
    JQuery判断数组中是否包含某个元素$.inArray("js", arr);
    Sql日期时间格式转换
    c#组元(Tuple)的使用
    逆向最大匹配分词算法C#
    [WEB API] CLIENT 指定请求及回应格式(XML/JSON)
    Jquery 将表单序列化为Json对象
    JS调试加断点
  • 原文地址:https://www.cnblogs.com/hll2008/p/2266672.html
Copyright © 2011-2022 走看看