使用Scroller类实现图片的循环浏览

来源:互联网 发布:大数据时代的特点 编辑:程序博客网 时间:2024/06/04 22:58

        本文结合Scroller类和ViewGroup的dispatchdraw函数来实现图片的循环浏览。本文并没有讲解Scroller类的用法,大家最好在看之前先熟悉下Scroller类的用法。好了,先来看demo的布局吧,如下:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:paddingBottom="@dimen/activity_vertical_margin"    android:paddingLeft="@dimen/activity_horizontal_margin"    android:paddingRight="@dimen/activity_horizontal_margin"    android:paddingTop="@dimen/activity_vertical_margin"    tools:context=".MainActivity" >    <com.example.scollerdemo.MultiViewGroup        android:id="@+id/mvg"        android:layout_width="match_parent"        android:layout_height="match_parent" >        <ImageView            android:layout_width="match_parent"            android:layout_height="wrap_content"            android:background="@drawable/one"            android:scaleType="fitCenter" />        <ImageView            android:layout_width="match_parent"            android:layout_height="wrap_content"            android:background="@drawable/two"            android:scaleType="fitCenter" />        <ImageView            android:layout_width="match_parent"            android:layout_height="wrap_content"            android:background="@drawable/three"            android:scaleType="fitCenter" />        <ImageView            android:layout_width="match_parent"            android:layout_height="wrap_content"            android:background="@drawable/four"            android:scaleType="fitCenter" />        <ImageView            android:layout_width="match_parent"            android:layout_height="wrap_content"            android:background="@drawable/five"            android:scaleType="fitCenter" />    </com.example.scollerdemo.MultiViewGroup></RelativeLayout>

布局很简单,就是在一个自定义ViewGroup中放了五张ImageView图片。主要内容就是自定义的那个View了。

首先在实例化MultiGroupView时需要实例化一个Scroller类实例。接着需要对该viewgroup中的几张子view进行onMeasure,onLayout 以及 dispatchDraw。

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {Log.i(TAG, "--- start onMeasure --");// 设置该ViewGroup的大小int width = MeasureSpec.getSize(widthMeasureSpec);int height = MeasureSpec.getSize(heightMeasureSpec);setMeasuredDimension(width, height);int childCount = getChildCount();Log.i(TAG, "--- onMeasure childCount is -->" + childCount);for (int i = 0; i < childCount; i++) {View child = getChildAt(i);// 设置每个子视图的大小 , 即全屏//child.measure(width, height);measureChild(child, widthMeasureSpec, heightMeasureSpec);}}


在测量过程中,让每个子view的大小跟父视图大小一样大,测量完之后就开始layout的过程,如下
protected void onLayout(boolean changed, int l, int t, int r, int b) {// TODO Auto-generated method stubLog.i(TAG, "--- start onLayout --");int startLeft = 0; // 每个子视图的起始布局坐标int startTop = 0; int childCount = getChildCount();Log.i(TAG, "--- onLayout childCount is -->" + childCount);for (int i = 0; i < childCount; i++) {View child = getChildAt(i);startTop = (960-mListener.getBarHeight()-child.getMeasuredHeight())/2;child.layout(startLeft, startTop, startLeft + child.getMeasuredWidth(), startTop + child.getMeasuredHeight());startLeft = startLeft + child.getMeasuredWidth() ; //校准每个子View的起始布局位置}}

        在layout的过程中,让子视图一字排开,形成画廊的效果,便于下面我们手动滑动时能够像图库那样一张张的浏览。接着就是dispatchdraw的过程,其实在测量和布局好之后,系统会自动按照测量的大小和布局的效果进行分配和绘制,这里为什么要重写dispatchdraw呢,主要是要让我们能够循环浏览,当我们浏览到最后一张图片时,如果不进行额外的绘制,下一张图片就是一张空白,同理从第一张图片右滑到最后一张时看到的也是一张空白。因此我们在当前显示图片为第一张并且在往右滑时要分别绘制出最后一张图片和第一张图片。在从最后一张图片左滑到第一张图片时要分别绘制出最后一张图片和第一张图片。同时滑动完毕之后还要立即将scroller的位置切换到真正的当前图片的位置。来看dispatchdraw的代码

protected void dispatchDraw(Canvas canvas) {// TODO Auto-generated method stublong drawingTime = getDrawingTime();        int width = getWidth();        float scrollPos = (float) getScrollX() / width;        Log.d(TAG,"---- scrollPos="+scrollPos);        boolean endlessScrolling = true;        int leftScreen;        int rightScreen;        boolean isScrollToRight = false;        int childCount = getChildCount();        if (scrollPos < 0 && endlessScrolling) {            leftScreen = childCount - 1;            rightScreen = 0;        } else {            leftScreen = Math.min( (int) scrollPos, childCount - 1 );            rightScreen = leftScreen + 1;            if (endlessScrolling) {                rightScreen = rightScreen % childCount;                isScrollToRight = true;            }        }        if (isScreenNoValid(leftScreen)) {             if (rightScreen == 0 && !isScrollToRight) { // 向左滑动,如果rightScreen是0                int offset = childCount * width;                canvas.translate(-offset, 0);                drawChild(canvas, getChildAt(leftScreen), drawingTime);                canvas.translate(+offset, 0);            } else {                drawChild(canvas, getChildAt(leftScreen), drawingTime);            }        }        if (scrollPos != leftScreen && isScreenNoValid(rightScreen)) {            if (endlessScrolling && rightScreen == 0  && isScrollToRight) {                 int offset = childCount * width;                 canvas.translate(+offset, 0);                 drawChild(canvas, getChildAt(rightScreen), drawingTime);                 canvas.translate(-offset, 0);            } else {                drawChild(canvas, getChildAt(rightScreen), drawingTime);            }        }    }

        上面是dispatchdraw的过程,刚才提到的在绘制完之后需要重新切换到当前图片真正的位置,这个是在computeScroll()函数中通过scrollTo实现的。

public void computeScroll() {// TODO Auto-generated method stub Log.d(TAG, this.toString() + " computeScroll-----------");         if (mScroller.computeScrollOffset())//如果mScroller没有调用startScroll,这里将会返回false。         {             scrollTo(mScroller.getCurrX(), 0);             postInvalidate();} else {if (mListener != null)mListener.updateCurrentX(mScroller.getFinalX());//mCurrentScreen = calculateCurrentPage();Log.d(TAG, "---- mCurrentScreen="+mCurrentScreen);             if (mCurrentScreen == -1 ) {                mCurrentScreen = getChildCount() - 1;                scrollTo(mCurrentScreen * getWidth(), getScrollY());                if (mListener != null)mListener.updateCurrentX(mCurrentScreen * getWidth());            } else if (mCurrentScreen == getChildCount() ) {                mCurrentScreen = 0;                scrollTo(0, getScrollY());                if (mListener != null)mListener.updateCurrentX(0);            } else {                mCurrentScreen = Math.max(0, Math.min(mCurrentScreen, getChildCount() - 1));            }}super.computeScroll();}


        computeScroll()是在滑动的过程中不断调用的,从而不断改变当前图片的位置,mScroller.computeScrollOffset()这个函数如果为true,说明还在改变的过程当中,如果为false则说明变化完成,此时需要根据当前屏幕来最终确定需要停止的位置。在实现了图片的循环浏览之后,下面来看下如何滑动的过程,主要在onTouchEvent()函数中实现的。


public boolean onTouchEvent(MotionEvent ev) {// TODO Auto-generated method stubif (getChildCount() <= 0) return super.onTouchEvent(ev);        acquireVelocityTrackerAndAddMovement(ev);        final int action = ev.getAction();        switch (action & MotionEvent.ACTION_MASK) {        case MotionEvent.ACTION_DOWN:        mCurrentScreen = calculateCurrentPage();            // * If being flinged and user touches, stop the fling. isFinished            // * will be false if being flinged.                         if (!mScroller.isFinished()) {                mScroller.abortAnimation();            }            // Remember where the motion event started            mDownMotionX = mLastMotionX = ev.getX();            mLastMotionXRemainder = 0;            mTotalMotionX = 0;            mActivePointerId = ev.getPointerId(0);            if (mTouchState == TOUCH_STATE_SCROLLING) {                Log.d(TAG,"---- touch down state is TOUCH_STATE_SCROLLING");//pageBeginMoving();            }            break;        case MotionEvent.ACTION_MOVE:            if (mTouchState == TOUCH_STATE_SCROLLING) {                // Scroll to follow the motion event                initiateScroll(ev);            } else {                determineScrollingStart(ev);            }            break;        case MotionEvent.ACTION_UP:            if (mTouchState == TOUCH_STATE_SCROLLING) {                final int activePointerId = mActivePointerId;                final int pointerIndex = ev.findPointerIndex(activePointerId);                final float x = ev.getX(pointerIndex);                final VelocityTracker velocityTracker = mVelocityTracker;                velocityTracker.computeCurrentVelocity(1000, 2000);                int velocityX = (int) velocityTracker.getXVelocity(activePointerId);                final int deltaX = (int) (x - mDownMotionX);                final int pageWidth = MainActivity.WIDTH;//getScaledMeasuredWidth(getPageAt(mCurrentPage));                boolean isSignificantMove = Math.abs(deltaX) > pageWidth *                        SIGNIFICANT_MOVE_THRESHOLD;                mTotalMotionX += Math.abs(mLastMotionX + mLastMotionXRemainder - x);                boolean isFling = mTotalMotionX > MIN_LENGTH_FOR_FLING &&                        Math.abs(velocityX) > 750;                // In the case that the page is moved far to one direction and then is flung                // in the opposite direction, we use a threshold to determine whether we should                // just return to the starting page, or if we should skip one further.                boolean returnToOriginalPage = false;                if (Math.abs(deltaX) > pageWidth * RETURN_TO_ORIGINAL_PAGE_THRESHOLD &&                        Math.signum(velocityX) != Math.signum(deltaX) && isFling) {                    returnToOriginalPage = true;                }                int finalPage;                // We give flings precedence over large moves, which is why we short-circuit our                // test for a large move if a fling has been registered. That is, a large                // move to the left and fling to the right will register as a fling to the right.                final boolean isRtl = false;//getLayoutDirection() == 1;                boolean isDeltaXLeft = isRtl ? deltaX > 0 : deltaX < 0;                boolean isVelocityXLeft = isRtl ? velocityX > 0 : velocityX < 0;                if (((isSignificantMove && !isDeltaXLeft && !isFling) ||                        (isFling && !isVelocityXLeft)) /*&& mCurrentPage > 0*/) {                    finalPage = returnToOriginalPage ? mCurrentScreen : mCurrentScreen - 1;                    snapToPageWithVelocity(finalPage, velocityX);                                    } else if (((isSignificantMove && isDeltaXLeft && !isFling) ||                        (isFling && isVelocityXLeft)) /*&&                        mCurrentPage < getChildCount() - 1*/) {                    finalPage = returnToOriginalPage ? mCurrentScreen : mCurrentScreen + 1;                    snapToPageWithVelocity(finalPage, velocityX);                } else {                    //snapToDestination();                snapToPageWithVelocity(mCurrentScreen, velocityX);                }                }            mTouchState = TOUCH_STATE_REST;            mActivePointerId = -1;            releaseVelocityTracker();            break;        case MotionEvent.ACTION_CANCEL:            mTouchState = TOUCH_STATE_REST;            mActivePointerId = -1;            releaseVelocityTracker();            break;        }        return true;}

        相信大家对滑动的原理并不陌生,这里就不解释了,需下载该demo的代码,请点这里。

0 0
原创粉丝点击