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

来源:互联网 发布:手机淘宝降价提醒设置 编辑:程序博客网 时间:2024/05/17 20:29

直接效果图 ,更多内容可以参看:我的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 extendsViewGroup{
 
    privateLinearLayout view1;
    privateLinearLayout view2;
    privateLinearLayout view3;
     
 
    //速度跟踪
    privateVelocityTracker mVelocityTracker;
    privateint mMaximumVelocity;
     
    //手势临界速度,当速度超过这个时切换到下一屏
    privatestatic final int SNAP_VELOCITY = 100;
     
    //停止状态
    privatefinal static int TOUCH_STATE_REST = 0;
    //滚动状态
    privatefinal static int TOUCH_STATE_MOVING = 1;
    //减速停止状态
    privatefinal static int TOUCH_STATE_SLOWING = 2;
     
    //当前触摸状态
    privateint touchState = TOUCH_STATE_REST;
     
    privateboolean lock=false;
     
    privatefloat lastMotionX;
    privatefloat lastMotionY;
     
    privateContext context;
    privateList<LinearLayout> views;
    //是否移动了
    privateboolean isMoved;
    //长按的runnable
    privateRunnable mLongPressRunnable;
    //移动的阈值
    privatestatic final int TOUCH_SLOP=10;
     
    publicint width;
     
    publicint height;
     
    publicTouchPageView(Context context) {
        super(context);
        this.context=context;
        init();
    }
     
    privatevoid init()
    {
        views=new ArrayList<LinearLayout>();
        view1=newLinearLayout(context);
        view1.setBackgroundColor(Color.YELLOW);
        this.addView(view1);
        TextView tv=newTextView(context);
        tv.setText("测试");
        view1.addView(tv);
        views.add(view1);
         
         
        view2=newLinearLayout(context);
        view2.setBackgroundColor(Color.RED);
        this.addView(view2);
        views.add(view2);
         
        view3=newLinearLayout(context);
        view3.setBackgroundColor(Color.GREEN);
        this.addView(view3);
        views.add(view3);
         
        finalViewConfiguration configuration = ViewConfiguration.get(getContext());
        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
    }
     
     
    @Override
    protectedvoid onMeasure(intwidthMeasureSpec, intheightMeasureSpec){
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        finalint count = views.size();
        for(int i = 0; i < count; i++) {
            finalView child =views.get(i);
            child.measure(widthMeasureSpec,heightMeasureSpec);
        }
         
        intfinalWidth, finalHeight;
        finalWidth = measureWidth(widthMeasureSpec);
        finalHeight = measureHeight(heightMeasureSpec);
 
        this.width=finalWidth;
        this.height=finalHeight;
 
    }
     
    privateint measureWidth(intmeasureSpec) {
        intresult = 0;
        intspecMode = MeasureSpec.getMode(measureSpec);
        intspecSize = MeasureSpec.getSize(measureSpec);
 
        if(specMode == MeasureSpec.EXACTLY) {
            result = specSize;
        }else {
            result = specSize;
        }
 
        returnresult;
    }
     
    privateint measureHeight(intmeasureSpec) {
        intresult = 0;
        intspecMode = MeasureSpec.getMode(measureSpec);
        intspecSize = MeasureSpec.getSize(measureSpec);
 
        if(specMode == MeasureSpec.EXACTLY) {
            result = specSize;
        }else {
            result = specSize;
        }
        returnresult;
    }
 
    @Override
    protectedvoid onLayout(booleanchanged, int l, int t, int r, intb) {
        intchildLeft = -1;
        finalint count = views.size();
        //水平从左到右放置
        for(int i = 0; i < count; i++) {
            finalView child =views.get(i);
            if(child.getVisibility() != View.GONE) {
                finalint childWidth = child.getMeasuredWidth();
                if(childLeft==-1)
                {
                    childLeft=-childWidth;
                }
                child.layout(childLeft,0, childLeft + childWidth, child.getMeasuredHeight());
                childLeft += childWidth;
            }
        }
         
    }
     
    //绘制子元素
    @Override
    protectedvoid onDraw(Canvas canvas) {
        //水平从左到右放置
        intcount = views.size();
        for(int i = 0; i < count; i++) {
            View child =views.get(i);
            drawChild(canvas, child, getDrawingTime());
        }
    }
 
    @Override
    publicboolean onTouchEvent(MotionEvent ev){
         
        if(!lock)
        {
            if(mVelocityTracker == null) {
                mVelocityTracker = VelocityTracker.obtain();
            }
            mVelocityTracker.addMovement(ev);
             
            finalint action = ev.getAction();
            finalfloat x = ev.getX();
            finalfloat y = ev.getY();
             
            switch(action) {
            caseMotionEvent.ACTION_DOWN://按下去
                if(touchState==TOUCH_STATE_REST)
                {
                    //记录按下去的的x坐标
                    lastMotionX = x;
                    touchState=TOUCH_STATE_MOVING;
                     
                    isMoved=false;
                }
                 
                break;
            caseMotionEvent.ACTION_MOVE://拖动时
                if(touchState==TOUCH_STATE_MOVING)
                {
                    floatoffsetX=x-lastMotionX;
                    floatoffsetY=y-lastMotionY;
                     
                    if(isMoved)
                    {
                        lastMotionX=x;
                        lastMotionY=y;
 
                        finalint count = views.size();
                        //水平从左到右放置
                        for(int i = 0; i < count; i++) {
                            finalView child =views.get(i);
                            if(child.getVisibility() != View.GONE) {
                                finalint childWidth = child.getMeasuredWidth();
                                intchildLeft = child.getLeft()+(int)offsetX;
                                child.layout(childLeft,0, childLeft + childWidth, child.getMeasuredHeight());
                                childLeft += childWidth;
                            }
                        }
                    }
                    elseif(Math.abs(offsetX)>TOUCH_SLOP||Math.abs(offsetY)>TOUCH_SLOP)
                    {
                        //移动超过阈值,则表示移动了
                        isMoved=true;
                        removeCallbacks(mLongPressRunnable);
                    }
                }
                 
                break;
            caseMotionEvent.ACTION_UP://放开时
                //释放了
                removeCallbacks(mLongPressRunnable);
                 
                if(isMoved)
                {
                    if(touchState==TOUCH_STATE_MOVING)
                    {
                        touchState=TOUCH_STATE_SLOWING;
                        intsign=0;
                        finalVelocityTracker velocityTracker = mVelocityTracker;
                        //计算当前速度
                        velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
                        //x方向的速度
                        intvelocityX = (int) velocityTracker.getXVelocity();
                        if(velocityX > SNAP_VELOCITY)//足够的能力向左
                        {
                            sign=1;
                            Log.e("enough to move left","true");
                        }
                        elseif (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;
            }
        }
        returntrue;
    }
     
    intoffset=0;
    privatevoid moveToFitView(intsign)
    {
        booleanb=swapView(sign);
        if(true)
        {
            View view1=views.get(1);
            intleft=view1.getLeft();
            //int offset=0;
            if(left!=0)
            {
                offset=-1*left;
            }
             
            moveView();
        }
    }
     
    FlipAnimationHandler mAnimationHandler;
    intovv=40;
    privatevoid moveView()
    {
        finalint count = views.size();
         
        if(offset!=0)
        {
            intov=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++) {
                finalView child =views.get(i);
                finalint childWidth = child.getMeasuredWidth();
                intchildLeft = 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;
        }
    }
     
    classFlipAnimationHandler extendsHandler {
        @Override
        publicvoid handleMessage(Message msg) {
            TouchPageView.this.moveView();
        }
 
        publicvoid sleep(longmillis) {
            this.removeMessages(0);
            sendMessageDelayed(obtainMessage(0), millis);
        }
    }
     
    privateboolean swapView(intsign)
    {
        booleanb=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);
                intchildWidth=view2.getMeasuredWidth();
                intchildLeft=view2.getLeft()+childWidth;
                view3.layout(childLeft,0, childLeft + view3.getMeasuredWidth(), view3.getMeasuredHeight());
                b=true;
            }
        }
        elseif(sign==1)//向右
        {
            View view3=views.get(2);
            if(view3.getLeft()>view3.getMeasuredWidth())
            {
                swapViewIndex(sign);
                 
                View view1=views.get(0);
                View view2=views.get(1);
                intchildRight=view2.getLeft();
                intchildLeft=childRight-view1.getMeasuredWidth();
                view1.layout(childLeft,0, childRight, view1.getMeasuredHeight());
                b=true;
            }
        }
         
        returnb;
    }
     
    privatevoid swapViewIndex(intsign)
    {
        if(sign==-1)//向左
        {
            LinearLayout v=views.remove(0);
            views.add(v);
        }
        elseif(sign==1)//向右
        {
            LinearLayout v=views.remove(views.size()-1);
            views.add(0, v);
        }
    }
}
原创粉丝点击