• Android实现多页左右滑动效果,支持子view动态创建和cache


    要实现多页滑动效果,主要是需要处理onTouchEvent和onInterceptTouchEvent,要处理好touch事件的子控件和父控件的传递问题。滚动控制可以利用android的Scroller来实现。
    对于不清楚android Touch事件的传递过程的,先google一下。
    这里提供两种做法:
    1、自定义MFlipper控件,从ViewGroup继承,利用Scroller实现滚动,重点是onTouchEvent和onInterceptTouchEvent的重写,要注意什么时候该返回true,什么时候false。否则会导致界面滑动和界面内按钮点击事件相冲突。
    由于采用了ViewGroup来管理子view,只适合于页面数较少而且较固定的情况,因为viewgroup需要一开始就调用addView,把所有view都加进去并layout,太多页面会有内存问题。如果是页面很多,而且随时动态增长的话,就需要考虑对view做cache和动态创建,动态layout,具体做法参考下面的方法二;
     
    2、从AdapterView继承,参考Android自带ListView的实现,实现子view动态创建和cache,滑动效果等。源码如下:

    import android.content.Context;
    import android.util.AttributeSet;
    import android.util.Log;
    import android.util.SparseArray;
    import android.view.MotionEvent;
    import android.view.VelocityTracker;
    import android.view.View;
    import android.view.ViewConfiguration;
    import android.view.ViewGroup;
    import android.widget.AdapterView;
    import android.widget.BaseAdapter;
    import android.widget.Gallery;
    import android.widget.Scroller;
     
    /**
     * 自定义一个横向滚动的AdapterView,类似与全屏的Gallery,但是一次只滚动一屏,而且每一屏支持子view的点击处理
     * @author weibinke
     *
     */
    public class MultiPageSwitcher extends AdapterView<BaseAdapter> {
     
    private BaseAdapter mAdapter = null;
    private Scroller mScroller;
    private int mTouchSlop;
    private float mTouchStartX;
    private float mLastMotionX;
    private final static String TAG = "MultiPageSwitcher";
     
    private int mLastScrolledOffset = 0;               
     
    /** User is not touching the list */
        private static final int TOUCH_STATE_RESTING = 0;
     
        /** User is scrolling the list */
        private static final int TOUCH_STATE_SCROLL = 2;
     
        private int mTouchState = TOUCH_STATE_RESTING;
    private int mHeightMeasureSpec;
    private int mWidthMeasureSpec;
    private int mSelectedPosition;
    private int mFirstPosition;                                //第一个可见view的position
    private int mCurrentSelectedPosition;
     
    private VelocityTracker mVelocityTracker;
    private static final int SNAP_VELOCITY = 600;
     
    protected RecycleBin mRecycler = new RecycleBin();
     
    private OnPostionChangeListener mOnPostionChangeListener = null;
     
    public MultiPageSwitcher(Context context, AttributeSet attrs) {
    super(context, attrs);
    mScroller = new Scroller(context);
    mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
    }
     
    @Override
    protected void onLayout(boolean changed, int left, int top, int right,
    int bottom) {
    // TODO Auto-generated method stub
    MLog.d("MultiPageSwitcher.onlayout start");
    super.onLayout(changed, left, top, right, bottom);
     
    if (mAdapter == null) {
    return ;
    }
     
    recycleAllViews();
    detachAllViewsFromParent();
    mRecycler.clear();
     
    fillAllViews();
     
    MLog.d("MultiPageSwitcher.onlayout end");
    }
     
    /**
     * 从当前可见的view向左边填充
     */
    private void fillToGalleryLeft() {
            int itemSpacing = 0;
            int galleryLeft = 0;
          
            // Set state for initial iteration
            View prevIterationView = getChildAt(0);
            int curPosition;
            int curRightEdge;
          
            if (prevIterationView != null) {
                curPosition = mFirstPosition - 1;
                curRightEdge = prevIterationView.getLeft() - itemSpacing;
            } else {
                // No children available!
                curPosition = 0;
                curRightEdge = getRight() - getLeft();
            }
                  
            while (curRightEdge > galleryLeft && curPosition >= 0) {
                prevIterationView = makeAndAddView(curPosition, curPosition - mSelectedPosition,
                        curRightEdge, false);
     
                // Remember some state
                mFirstPosition = curPosition;
              
                // Set state for next iteration
                curRightEdge = prevIterationView.getLeft() - itemSpacing;
                curPosition--;
            }
        }
      
        private void fillToGalleryRight() {
            int itemSpacing = 0;
            int galleryRight = getRight() - getLeft();
            int numChildren = getChildCount();
            int numItems = mAdapter.getCount();
          
            // Set state for initial iteration
            View prevIterationView = getChildAt(numChildren - 1);
            int curPosition;
            int curLeftEdge;
          
            if (prevIterationView != null) {
                curPosition = mFirstPosition + numChildren;
                curLeftEdge = prevIterationView.getRight() + itemSpacing;
            } else {
                mFirstPosition = curPosition = numItems - 1;
                curLeftEdge = 0;
            }
                  
            while (curLeftEdge < galleryRight && curPosition < numItems) {
                prevIterationView = makeAndAddView(curPosition, curPosition - mSelectedPosition,
                        curLeftEdge, true);
     
                // Set state for next iteration
                curLeftEdge = prevIterationView.getRight() + itemSpacing;
                curPosition++;
            }
        }
     
    /**
     *填充view
     */
    private void fillAllViews(){
    //先创建第一个view,使其居中显示
    if (mSelectedPosition >= mAdapter.getCount()&& mSelectedPosition > 0) {
    //处理被记录被删除导致当前选中位置超出记录数的情况
    mSelectedPosition = mAdapter.getCount() - 1;
    if(mOnPostionChangeListener != null){
    mCurrentSelectedPosition = mSelectedPosition;
    mOnPostionChangeListener.onPostionChange(this, mCurrentSelectedPosition);
    }
    }
     
    mFirstPosition = mSelectedPosition;
    mCurrentSelectedPosition = mSelectedPosition;
     
    View child = makeAndAddView(mSelectedPosition, 0, 0, true);
     
    int offset = getWidth() / 2 - (child.getLeft() + child.getWidth() / 2);
    child.offsetLeftAndRight(offset);
     
    fillToGalleryLeft();
    fillToGalleryRight();
     
    }
     
    /**
         * Obtain a view, either by pulling an existing view from the recycler or by
         * getting a new one from the adapter. If we are animating, make sure there
         * is enough information in the view's layout parameters to animate from the
         * old to new positions.
         *
         * @param position Position in the gallery for the view to obtain
         * @param offset Offset from the selected position
         * @param x X-coordintate indicating where this view should be placed. This
         *        will either be the left or right edge of the view, depending on
         *        the fromLeft paramter
         * @param fromLeft Are we posiitoning views based on the left edge? (i.e.,
         *        building from left to right)?
         * @return A view that has been added to the gallery
         */
        private View makeAndAddView(int position, int offset, int x,
                boolean fromLeft) {
     
            View child;
     
    //        child = mRecycler.get(position);
    //        if (child != null) {
    //            // Position the view
    //            setUpChild(child, offset, x, fromLeft);
    //
    //            return child;
    //        }
    //
    //        // Nothing found in the recycler -- ask the adapter for a view
            child = mAdapter.getView(position, null, this);
     
            // Position the view
            setUpChild(child, offset, x, fromLeft);
     
            return child;
        }
      
        @Override
        protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
            /*
             * Gallery expects Gallery.LayoutParams.
             */
            return new Gallery.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
                    ViewGroup.LayoutParams.WRAP_CONTENT);
        }
     
        /**
         * Helper for makeAndAddView to set the position of a view and fill out its
         * layout paramters.
         *
         * @param child The view to position
         * @param offset Offset from the selected position
         * @param x X-coordintate indicating where this view should be placed. This
         *        will either be the left or right edge of the view, depending on
         *        the fromLeft paramter
         * @param fromLeft Are we posiitoning views based on the left edge? (i.e.,
         *        building from left to right)?
         */
        private void setUpChild(View child, int offset, int x, boolean fromLeft) {
     
            // Respect layout params that are already in the view. Otherwise
            // make some up...
            Gallery.LayoutParams lp = (Gallery.LayoutParams)
                child.getLayoutParams();
            if (lp == null) {
                lp = (Gallery.LayoutParams) generateDefaultLayoutParams();
            }
     
            addViewInLayout(child, fromLeft ? -1 : 0, lp);
     
            child.setSelected(offset == 0);
     
            // Get measure specs
            int childHeightSpec = ViewGroup.getChildMeasureSpec(mHeightMeasureSpec,
                    0, lp.height);
            int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,
                    0, lp.width);
     
            // Measure child
            child.measure(childWidthSpec, childHeightSpec);
     
            int childLeft;
            int childRight;
     
            // Position vertically based on gravity setting
            int childTop = 0;
            int childBottom = childTop + child.getMeasuredHeight();
     
            int width = child.getMeasuredWidth();
            if (fromLeft) {
                childLeft = x;
                childRight = childLeft + width;
            } else {
                childLeft = x - width;
                childRight = x;
            }
     
            child.layout(childLeft, childTop, childRight, childBottom);
        }
      
      
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
                // TODO Auto-generated method stub
                super.onMeasure(widthMeasureSpec, heightMeasureSpec);
               
                mWidthMeasureSpec = widthMeasureSpec;
                mHeightMeasureSpec = heightMeasureSpec;
        }
     
        @Override
        public int getCount() {
                // TODO Auto-generated method stub
                return mAdapter.getCount();
        }
     
    @Override
    public BaseAdapter getAdapter() {
    // TODO Auto-generated method stub
    return mAdapter;
    }
     
    @Override
    public void setAdapter(BaseAdapter adapter) {
    // TODO Auto-generated method stub
    mAdapter = adapter;
     
    removeAllViewsInLayout();
     
    requestLayout();
    }
     
    @Override
    public View getSelectedView() {
    // TODO Auto-generated method stub
    return null;
    }
     
    @Override
    public void setSelection(int position) {
    // TODO Auto-generated method stub
    }
     
    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
    if (!mScroller.isFinished()) {
    return true;
    }
     
    final int action = event.getAction();
    MLog.d("onInterceptTouchEvent action = "+event.getAction());
     
    if (MotionEvent.ACTION_DOWN == action) {
    startTouch(event);
     
    return false;
    }else if (MotionEvent.ACTION_MOVE == action) {
    return startScrollIfNeeded(event);
    }else if (MotionEvent.ACTION_UP == action || MotionEvent.ACTION_CANCEL == action) {
    mTouchState = TOUCH_STATE_RESTING;
     
    return false;
    }
    return false;
    }
     
    @Override
    public boolean onTouchEvent(MotionEvent event) {
     
    if (!mScroller.isFinished()) {
    return true;
    }
     
    if (mVelocityTracker == null) {
    mVelocityTracker = VelocityTracker.obtain();
    }
     
    mVelocityTracker.addMovement(event);
     
    MLog.d("onTouchEvent action = "+event.getAction());
    final int action = event.getAction();
    final float x = event.getX();
     
    if (MotionEvent.ACTION_DOWN == action) {
    startTouch(event);
     
    }else if (MotionEvent.ACTION_MOVE == action) {
    if (mTouchState == TOUCH_STATE_RESTING) {
    startScrollIfNeeded(event);
    }else if (mTouchState == TOUCH_STATE_SCROLL) {
    int deltaX = (int)(x - mLastMotionX);
    mLastMotionX = x;
     
    scrollDeltaX(deltaX);
     
    }
    }else if (MotionEvent.ACTION_UP == action || MotionEvent.ACTION_CANCEL == action) {
    if (mTouchState == TOUCH_STATE_SCROLL) {
    onUp(event);
    }
    }
    return true;
    }
     
     
    private void scrollDeltaX(int deltaX){
     
    //先把现有的view坐标移动
    for (int i = 0; i < getChildCount(); i++) {
    getChildAt(i).offsetLeftAndRight(deltaX);
    }
     
    boolean toLeft = (deltaX < 0);
    detachOffScreenChildren(toLeft);
     
    if (deltaX < 0) {
    //sroll to right
    fillToGalleryRight();
    }else {
    fillToGalleryLeft();
    }
     
    invalidate();
     
    int position = calculteCenterItem() + mFirstPosition;
    if (mCurrentSelectedPosition != position) {
    mCurrentSelectedPosition = position;
    if (mOnPostionChangeListener != null) {
    mOnPostionChangeListener.onPostionChange(this, mCurrentSelectedPosition);
    }
    }
    }
     
    private void onUp(MotionEvent event){
     
     
    final VelocityTracker velocityTracker = mVelocityTracker; 
    velocityTracker.computeCurrentVelocity(1000); 
    int velocityX = (int) velocityTracker.getXVelocity(); 
     
    MLog.d( "onUp velocityX:"+velocityX);
     
    if (velocityX < -SNAP_VELOCITY && mSelectedPosition < mAdapter.getCount() - 1) {
    if (scrollToChild(mSelectedPosition + 1)) {
    mSelectedPosition ++;
    }
    }else if (velocityX > SNAP_VELOCITY && mSelectedPosition > 0) {
    if (scrollToChild(mSelectedPosition - 1)) {
    mSelectedPosition --;
    }
    }else{
    int position = calculteCenterItem();
    int newpostion = mFirstPosition + position;
    if (scrollToChild(newpostion)) {
    mSelectedPosition = newpostion;
    }
    }
     
    if (mVelocityTracker != null) { 
                mVelocityTracker.recycle(); 
                mVelocityTracker = null; 
            } 
     
    mTouchState = TOUCH_STATE_RESTING;
    }
     
    /**
     * 计算最接近中心点的view
     * @return
     */
    private int calculteCenterItem(){
    View child = null;
    int lastpostion = 0;
    int lastclosestDistance = 0;
    int viewCenter = getLeft() + getWidth() / 2;
    for (int i = 0; i < getChildCount(); i++) {
    child = getChildAt(i);
    if (child.getLeft() < viewCenter && child.getRight() > viewCenter ) {
    lastpostion = i;
    break;
    }else {
    int childClosestDistance = Math.min(Math.abs(child.getLeft() - viewCenter), Math.abs(child.getRight() - viewCenter));
    if (childClosestDistance < lastclosestDistance) {
    lastclosestDistance = childClosestDistance;
    lastpostion = i;
    }
    }
    }
     
    return lastpostion;
    }
     
    public void moveNext(){
    if (!mScroller.isFinished()) {
    return;
    }
     
    if (0 <= mSelectedPosition && mSelectedPosition < mAdapter.getCount() - 1) {
    if (scrollToChild(mSelectedPosition + 1)) {
    mSelectedPosition ++;
    }else {
    makeAndAddView(mSelectedPosition + 1, 1, getWidth(), true);
    if (scrollToChild(mSelectedPosition + 1)) {
    mSelectedPosition ++;
    }
    }
    }
    }
     
    public void movePrevious(){
    if (!mScroller.isFinished()) {
    return;
    }
     
    if (0 < mSelectedPosition && mSelectedPosition < mAdapter.getCount()) {
    if (scrollToChild(mSelectedPosition -1)) {
    mSelectedPosition --;
    }else {
    makeAndAddView(mSelectedPosition - 1, -1, 0, false);
    mFirstPosition = mSelectedPosition - 1;
    if (scrollToChild(mSelectedPosition - 1)) {
    mSelectedPosition --;
    }
    }
    }
    }
     
    private boolean scrollToChild(int position){
    MLog.d( "scrollToChild positionm,FirstPosition,childcount:"+position + "," + mFirstPosition+ "," + getChildCount());
    View child = getChildAt(position - mFirstPosition );
    if (child != null) {
    int distance = getWidth() / 2 - (child.getLeft() + child.getWidth() / 2);
     
    mLastScrolledOffset = 0;
    mScroller.startScroll(0, 0, distance, 0,200);
    invalidate();
     
    return true;
    }
     
    MLog.d( "scrollToChild some error happened");
     
     
    return false;
    }
     
    @Override
    public void computeScroll() {
    // TODO Auto-generated method stub
    if (mScroller.computeScrollOffset()) {
    int scrollX = mScroller.getCurrX();
    //                        Mlog.d("MuticomputeScroll ," + scrollX);
     
    scrollDeltaX(scrollX - mLastScrolledOffset);
    mLastScrolledOffset = scrollX;
    postInvalidate();
    }
    }
     
    private void startTouch(MotionEvent event){
    mTouchStartX = event.getX();
     
    mTouchState = mScroller.isFinished()? TOUCH_STATE_RESTING : TOUCH_STATE_SCROLL;
     
    mLastMotionX = mTouchStartX;
    }
     
    private boolean startScrollIfNeeded(MotionEvent event){
    final int xPos = (int)event.getX();
            mLastMotionX = event.getX();
            if (xPos < mTouchStartX - mTouchSlop
                    || xPos > mTouchStartX + mTouchSlop
                  ) {
                // we've moved far enough for this to be a scroll
                mTouchState = TOUCH_STATE_SCROLL;
                return true;
            }
            return false;
    }
     
    /**
         * Detaches children that are off the screen (i.e.: Gallery bounds).
         *
         * @param toLeft Whether to detach children to the left of the Gallery, or
         *            to the right.
         */
        private void detachOffScreenChildren(boolean toLeft) {
            int numChildren = getChildCount();
            int start = 0;
            int count = 0;
          
            int firstPosition = mFirstPosition;
            if (toLeft) {
                final int galleryLeft = 0;
                for (int i = 0; i < numChildren; i++) {
                    final View child = getChildAt(i);
                    if (child.getRight() >= galleryLeft) {
                        break;
                    } else {
                        count++;
                        mRecycler.put(firstPosition + i, child);
                    }
                }
            } else {
                final int galleryRight = getWidth();
                for (int i = numChildren - 1; i >= 0; i--) {
                    final View child = getChildAt(i);
                    if (child.getLeft() <= galleryRight) {
                        break;
                    } else {
                        start = i;
                        count++;
                        mRecycler.put(firstPosition + i, child);
                    }
                }
            }
     
            detachViewsFromParent(start, count);
          
            if (toLeft) {
                mFirstPosition += count;
            }
          
            mRecycler.clear();
        }
      
        public void setOnPositionChangeListen(OnPostionChangeListener onPostionChangeListener){
                mOnPostionChangeListener = onPostionChangeListener;
        }
      
        public int getCurrentSelectedPosition(){
                return mCurrentSelectedPosition;
        }
      
        /**
         * 刷新数据,本来想用AdapterView.AdapterDataSetObserver机制来实现的,但是整个逻辑移植比较麻烦,就暂时用这个替代了
         */
        public void updateData(){
                requestLayout();
        }
      
        private void recycleAllViews() {
            int childCount = getChildCount();
            final RecycleBin recycleBin = mRecycler;
     
            // All views go in recycler
            for (int i=0; i<childCount; i++) {
                View v = getChildAt(i);
                int index = mFirstPosition + i;
                recycleBin.put(index, v);
            }
        }
      
        class RecycleBin {
            private SparseArray<View> mScrapHeap = new SparseArray<View>();
     
            public void put(int position, View v) {
                    if (mScrapHeap.get(position) != null) {
    Log.e(TAG,"RecycleBin put error.");
    }
                mScrapHeap.put(position, v);
            }
          
            View get(int position) {
                // System.out.print("Looking for " + position);
                View result = mScrapHeap.get(position);
                if (result != null) {
                        MLog.d("RecycleBin get hit.");
                    mScrapHeap.delete(position);
                } else {
                        MLog.d("RecycleBin get Miss.");
                }
                return result;
            }
          
            View peek(int position) {
                // System.out.print("Looking for " + position);
                return mScrapHeap.get(position);
            }
          
            void clear() {
                final SparseArray<View> scrapHeap = mScrapHeap;
                final int count = scrapHeap.size();
                for (int i = 0; i < count; i++) {
                    final View view = scrapHeap.valueAt(i);
                    if (view != null) {
                        removeDetachedView(view, true);
                    }
                }
                scrapHeap.clear();
            }
        }  
        public interface OnPostionChangeListener{
                abstract public void onPostionChange(View v,int position);
        }
    }
  • 相关阅读:
    Spring MVC @RequestMapping注解详解
    (转)Cesium教程系列汇总
    spring boot +mybatis(通过properties配置) 集成
    SpringBoot2.0之四 简单整合MyBatis
    在Windows下使用Git+TortoiseGit+码云管理项目代码
    TortoiseGit之配置密钥
    Spring Boot 学习之路二 配置文件 application.yml
    SpringBoot学习笔记(2) Spring Boot的一些配置
    【入门】Spring-Boot项目配置Mysql数据库
    Spring 的application.properties项目配置与注解
  • 原文地址:https://www.cnblogs.com/zhujiabin/p/5567180.html
Copyright © 2020-2023  润新知