• android 横向list特效——规格滑动


      首先,这个是在github开源项目HorizontalListView上作得修改,这个开源项目的下载地址我忘记了,贴一个引用网址:http://code.eoe.cn/233。

      首先来说一下什么叫规格滑动:

      上图就是规格滑动的合法状态:恰好显示一定数量的item,并且没有item处于一半显示一半在屏幕外的状态。这样说还不是很清楚,那么再贴一张非法状态:

      

      所谓规格滑动,就是每次滑动结束之后必然停留在合法状态,如果是非法状态,则会自动滑动到最近的合法状态位置上。一次滚动之后ListView要么没有位移,要么位移的距离为item宽度的整数倍,这样说理解了吧。如果滚动完成之后,一个图标有一半以上的距离滑出了屏幕,那么就再滑一点让它彻底移出屏幕,反之,如果这个图标滑出屏幕的距离不足一半宽度,就把它“拉回来”,让它完整显示出来。

      下面贴出修改之后的HorizontalListView类代码:

    package com.example.test3;
    
    import java.util.LinkedList;
    import java.util.Queue;
    
    import android.content.Context;
    import android.database.DataSetObserver;
    import android.graphics.Rect;
    import android.util.AttributeSet;
    import android.util.DisplayMetrics;
    import android.view.GestureDetector;
    import android.view.GestureDetector.OnGestureListener;
    import android.view.MotionEvent;
    import android.view.View;
    import android.widget.AdapterView;
    import android.widget.ListAdapter;
    import android.widget.Scroller;
    
    public class HorizontalListView extends AdapterView<ListAdapter> {
    
        public boolean mAlwaysOverrideTouch = true;
        protected ListAdapter mAdapter;
        private int mLeftViewIndex = -1; // 左边View的下标
        private int mRightViewIndex = 0; // 右边View的下标
        protected int mCurrentX; // 当前x坐标
        protected int mNextX; // 一次移动后x坐标
        private int mMaxX = Integer.MAX_VALUE; // x坐标最大值
        private int mDisplayOffset = 0; // 偏移量
        protected Scroller mScroller;
        private GestureDetector mGesture;
    //    private Queue<View> mRemovedViewQueue = new LinkedList<View>();
        private Queue<View> mRemovedLeftViewQueue = new LinkedList<View>();
        private Queue<View> mRemovedRightViewQueue = new LinkedList<View>();
        private OnItemSelectedListener mOnItemSelected;
        private OnItemClickListener mOnItemClicked;
        private OnItemLongClickListener mOnItemLongClicked;
        private boolean mDataChanged = false;
        // 获取屏幕宽度
        private DisplayMetrics metrics = getResources().getDisplayMetrics();
        private int screenWidth = metrics.widthPixels;
    
        /**
         * minItemWidth:表示每个item的最小宽度
         * itemCount   :一屏能显示的item数量
         * itemWidth   :每个item的实际宽度
         */
        public static final int MIN_WIDTH_IN_DP = 70;
        private final int minItemWidth = (int) (metrics.density * MIN_WIDTH_IN_DP);
        private final int itemCount = screenWidth / minItemWidth;
        private final int itemWidth = (int) (screenWidth * 1.0 / itemCount);
    
        public HorizontalListView(Context context, AttributeSet attrs) {
            super(context, attrs);
            initView();
        }
    
        private synchronized void initView() {
            mLeftViewIndex = -1;
            mRightViewIndex = 0;
            mDisplayOffset = 0;
            mCurrentX = 0;
            mNextX = 0;
            mMaxX = Integer.MAX_VALUE;
            mScroller = new Scroller(getContext());
            mGesture = new GestureDetector(getContext(), mOnGesture);
        }
    
        @Override
        public void setOnItemSelectedListener(
                AdapterView.OnItemSelectedListener listener) {
            mOnItemSelected = listener;
        }
    
        @Override
        public void setOnItemClickListener(AdapterView.OnItemClickListener listener) {
            mOnItemClicked = listener;
        }
    
        @Override
        public void setOnItemLongClickListener(
                AdapterView.OnItemLongClickListener listener) {
            mOnItemLongClicked = listener;
        }
    
        private DataSetObserver mDataObserver = new DataSetObserver() {
    
            @Override
            public void onChanged() {
                synchronized (HorizontalListView.this) {
                    mDataChanged = true;
                }
                invalidate();
                requestLayout();
            }
    
            @Override
            public void onInvalidated() {
                reset();
                invalidate();
                requestLayout();
            }
    
        };
        public boolean isOnDown = false;
    
        @Override
        public ListAdapter getAdapter() {
            return mAdapter;
        }
    
        @Override
        public View getSelectedView() {
            // TODO: implement
            return null;
        }
    
        @Override
        public void setAdapter(ListAdapter adapter) {
            if (mAdapter != null) {
                mAdapter.unregisterDataSetObserver(mDataObserver);
            }
            mAdapter = adapter;
            mAdapter.registerDataSetObserver(mDataObserver);
            reset();
        }
    
        private synchronized void reset() {
            initView();
            removeAllViewsInLayout();
            requestLayout();
        }
    
        @Override
        public void setSelection(int position) {
            // TODO: implement
        }
    
        private void addAndMeasureChild(final View child, int viewPos) {
            LayoutParams params = child.getLayoutParams();
            if (params == null) {
                params = new LayoutParams(LayoutParams.MATCH_PARENT,
                        LayoutParams.MATCH_PARENT);
            }
    
            addViewInLayout(child, viewPos, params, true);
            child.measure(
                    MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST),
                    MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.AT_MOST));
        }
    
        @Override
        protected synchronized void onLayout(boolean changed, int left, int top,
                int right, int bottom) {
            super.onLayout(changed, left, top, right, bottom);
    
            if (mAdapter == null) {
                return;
            }
    
            if (mDataChanged) {
                int oldCurrentX = mCurrentX;
                initView();
                removeAllViewsInLayout();
                mNextX = oldCurrentX;
    
                // by zhangchi
                //在这个if()语句中执行了initView()方法,表示如果adapter中数据变更则重新执行initView();
                //initView()方法中重新初始化了mScroller,mScroller.getCurrX()的值将变为0;
                //但是这里我点击某个图标之后仅仅希望图标的背景改变,不希望listView回到起点,所以要执行下面语句
                mScroller.setFinalX(oldCurrentX);
                //
    
                mDataChanged = false;
    
            }
    
            // computeScrollOffset()方法,如果返回true表示(滚动)动画还没有完成
            // 当你想知道当前位置(location)时调用该方法。
            if (mScroller.computeScrollOffset()) {
                int scrollx = mScroller.getCurrX();
                mNextX = scrollx;
            }
    
            // 如果滑动位置要超过边界值,则把当前位置设为边界值,并强制终止滑动
            if (mNextX <= 0) {
                mNextX = 0;
                mScroller.forceFinished(true);
            }
            if (mNextX >= mMaxX) {
                mNextX = mMaxX;
                mScroller.forceFinished(true);
            }
    
            //dx表示一次滑动的位移
            int dx = mCurrentX - mNextX;
    
            removeNonVisibleItems(dx);
            fillList(dx);
            positionItems(dx);
    
            mCurrentX = mNextX;
    
            //如果滑动没有停止
            if (!mScroller.isFinished()) {
                post(new Runnable() {
                    @Override
                    public void run() {
                        requestLayout();
                    }
                });
    
            } 
            /**
             * isTouched是一个boolean变量,初始化默认为false。
             * 在onTouch的ACTION_DOWN和ACTION_UP时会赋值为true
             * 在onFling方法中会赋值为false。
             * 也就是说,滑动分为两种:
             * 1.手指迅速滑动后离开屏幕,会触发onFling方法,系统通过计算自动完成剩下的滑动,此时isTouched为false;
             * 2.手指一直在屏幕滑动,滑到指定位置后才松开手指,不再触发onFling方法,此时isTouched为true;
             * 下面的代码可以使情况1自动到达最近的合法位置
             */
            else if (!isTouched) {
                isTouched = true;
                //下面几行计算dx:当前位置与最近合法位置之间的偏移量
                dx = mScroller.getCurrX() % itemWidth;
                if (dx != 0) {
                    if (dx > itemWidth / 2) {
                        dx = dx - itemWidth;
                    }
                    /**
                     * void android.widget.Scroller.startScroll(int startX, int startY, int dx, int dy)
                     * 该方法可以是Scroller从(startX,startY)滚动到(startX+dx,startY+dy),滚动时间为默认的250ms
                     * startX:滚动起始x坐标 
                     * startY:滚动起始y坐标 
                     * dx:滚动x坐标偏移量,正直表示向左滚动
                     * dy:滚动y坐标偏移量,正直表示向上滚动
                     */
                    mScroller.startScroll(mScroller.getCurrX(), mScroller.getCurrY(), -dx, 0);
    
                    post(new Runnable() {
    
                        @Override
                        public void run() {
                            // TODO Auto-generated method stub
                            requestLayout();
                        }
                    });
                }
            }
        }
    
        // 填充列表,fillList()传入一次移动的偏移量dx
        private void fillList(final int dx) {
            int edge = 0;
            View child = getChildAt(getChildCount() - 1);
            if (child != null) {
                edge = child.getRight();
            }
            fillListRight(edge, dx);
    
            edge = 0;
            child = getChildAt(0);
            if (child != null) {
                edge = child.getLeft();
            }
            fillListLeft(edge, dx);
    
        }
    
        private void fillListRight(int rightEdge, final int dx) {
            // rightEdge是为了计算mMaxX(最大长度的X坐标)
            while (rightEdge + dx < getWidth()
                    && mRightViewIndex < mAdapter.getCount()) {
                // Queue的Object poll()方法:获取队列头部的元素,并删除该元素。如果此队列为空,则返回null。
                /**
                 * 下面这个mAdapter正是setAdapter传递进来的适配器,getView方法自然是自定义adapter时自己写的方法。
                 * 原程序只用了一个缓存队列,但我使用时不知道为什么会有图片错乱的情况,所以此处我增加了一个缓存队列,
                 * 两个队列分别缓存左右两边移除的view
                 */
                View child = mAdapter.getView(mRightViewIndex,
                        mRemovedRightViewQueue.poll(), this);
                addAndMeasureChild(child, -1);
                rightEdge += child.getMeasuredWidth();
    
                // 如果右边View的下标已经是适配器的总数,则计算出mMaxX
                if (mRightViewIndex == mAdapter.getCount() - 1) {
                    mMaxX = mCurrentX + rightEdge - getWidth();
                }
    
                if (mMaxX < 0) {
                    mMaxX = 0;
                }
                mRightViewIndex++;
            }
    
        }
    
        private void fillListLeft(int leftEdge, final int dx) {
            while (leftEdge + dx > 0 && mLeftViewIndex >= 0) {
                View child = mAdapter.getView(mLeftViewIndex,
                        mRemovedLeftViewQueue.poll(), this);
                addAndMeasureChild(child, 0);
                leftEdge -= child.getMeasuredWidth();
                mLeftViewIndex--;
                mDisplayOffset -= child.getMeasuredWidth();
            }
        }
    
        // 该方法从list两端找到不在屏幕内显示的子View,并作相应处理
        private void removeNonVisibleItems(final int dx) {
            // 从第一个子View开始,getChild()方法找到的是可见的第一个View。
            View child = getChildAt(0);
            // 如果子View的右边界+偏移量<0,即该子View没在屏幕显示范围内(屏幕左边界之外)
            while (child != null && child.getRight() + dx <= 0) {
                mDisplayOffset += child.getMeasuredWidth();
                // Queue接口中的boolean offer(Object e)方法:将指定元素加入此队列的尾部。
                // 当使用有容量限制的队列时,此方法通常比add(Object e)方法更好。
                // 下面的语句把当前View加入removed组件队列之中。
                mRemovedLeftViewQueue.offer(child);
                // 将当前View从Layout中移除。
                removeViewInLayout(child);
                // 最左边View下标加一。
                mLeftViewIndex++;
                // child移动到下一个View
                child = getChildAt(0);
            }
    
            // 右边的操作与左边类似
            child = getChildAt(getChildCount() - 1);
            while (child != null && child.getLeft() + dx >= getWidth()) {
                mRemovedRightViewQueue.offer(child);
                removeViewInLayout(child);
                mRightViewIndex--;
                child = getChildAt(getChildCount() - 1);
            }
        }
    
        private void positionItems(final int dx) {
            if (getChildCount() > 0) {
                mDisplayOffset += dx;
                int left = mDisplayOffset;
                for (int i = 0; i < getChildCount(); i++) {
                    View child = getChildAt(i);
                    int childWidth = child.getMeasuredWidth();
                    child.layout(left, 0, left + childWidth,
                            child.getMeasuredHeight());
                    left += childWidth + child.getPaddingRight();
                }
            }
        }
    
        public synchronized void scrollTo(int x) {
            mScroller.startScroll(mNextX, 0, x - mNextX, 0);
            requestLayout();
        }
    
        @Override
        public boolean dispatchTouchEvent(MotionEvent ev) {
            boolean handled = super.dispatchTouchEvent(ev);
            handled |= mGesture.onTouchEvent(ev);
            return handled;
        }
    
        protected boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
                float velocityY) {
            synchronized (HorizontalListView.this) {
                mScroller.fling(mNextX, 0, (int) -velocityX, 0, 0, mMaxX, 0, 0);
            }
            requestLayout();
            
            return true;
        }
    
        // 无论是否在滚动,如果有手指按下事件则立即停止滚动
        protected boolean onDown(MotionEvent e) {
            //如果滑动没有结束,把isOnDown设为true,在onItemClickListener中取出isOnDown做判断:
            //1.如果isOnDown为true,则点击事件作用为停止滑动,不能响应onItemClick事件
            //2.如果isOnDown为false,则点击之前滑动已经停止,可以响应onItemClick事件
            isOnDown = !mScroller.isFinished();
            mScroller.forceFinished(true);
            return true;
        }
    
        private OnGestureListener mOnGesture = new GestureDetector.SimpleOnGestureListener() {
    
            @Override
            public boolean onDown(MotionEvent e) {
                return HorizontalListView.this.onDown(e);
            }
    
            @Override
            public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
                    float velocityY) {
                isTouched = false;
                return HorizontalListView.this
                        .onFling(e1, e2, velocityX, velocityY);
            }
    
            @Override
            public boolean onScroll(MotionEvent e1, MotionEvent e2,
                    float distanceX, float distanceY) {
    
                synchronized (HorizontalListView.this) {
                    mNextX += (int) distanceX;
                }
                requestLayout();
                return true;
            }
    
            @Override
            public boolean onSingleTapConfirmed(MotionEvent e) {
                for (int i = 0; i < getChildCount(); i++) {
                    View child = getChildAt(i);
                    if (isEventWithinView(e, child)) {
                        if (mOnItemClicked != null) {
                            mOnItemClicked.onItemClick(HorizontalListView.this,
                                    child, mLeftViewIndex + 1 + i,
                                    mAdapter.getItemId(mLeftViewIndex + 1 + i));
                        }
                        if (mOnItemSelected != null) {
                            mOnItemSelected.onItemSelected(HorizontalListView.this,
                                    child, mLeftViewIndex + 1 + i,
                                    mAdapter.getItemId(mLeftViewIndex + 1 + i));
                        }
                        break;
                    }
    
                }
                return true;
            }
    
            @Override
            public void onLongPress(MotionEvent e) {
                int childCount = getChildCount();
                for (int i = 0; i < childCount; i++) {
                    View child = getChildAt(i);
                    if (isEventWithinView(e, child)) {
                        if (mOnItemLongClicked != null) {
                            mOnItemLongClicked.onItemLongClick(
                                    HorizontalListView.this, child, mLeftViewIndex
                                            + 1 + i,
                                    mAdapter.getItemId(mLeftViewIndex + 1 + i));
                        }
                        break;
                    }
    
                }
            }
    
            private boolean isEventWithinView(MotionEvent e, View child) {
                Rect viewRect = new Rect();
                int[] childPosition = new int[2];
                child.getLocationOnScreen(childPosition);
                int left = childPosition[0];
                int right = left + child.getWidth();
                int top = childPosition[1];
                int bottom = top + child.getHeight();
                viewRect.set(left, top, right, bottom);
                return viewRect.contains((int) e.getRawX(), (int) e.getRawY());
            }
        };
        private boolean isTouched = false;
        private int downX, upX;                                //downX-upX计算手指滑动的距离
        
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            // TODO Auto-generated method stub
    
            int ea = event.getAction();
            switch (ea) {
            case MotionEvent.ACTION_DOWN:
                isTouched = true;
                downX = (int) event.getRawX();
                if (mScroller.getCurrX() % itemWidth != 0) {
                    return true;
                }
                break;
            case MotionEvent.ACTION_MOVE:
                break;
            case MotionEvent.ACTION_UP:
                upX = (int) event.getRawX();
                int dx = (-(upX - downX) + mScroller.getCurrX()) % itemWidth;
                // dx不为0表示当前不处于合法位置
                if (dx != 0) {
                    if (dx > itemWidth / 2) {
                        dx = dx - itemWidth;
                    }
                    mScroller.startScroll(mScroller.getCurrX() - (upX - downX),
                            mScroller.getCurrY(), -dx, 0);
                    post(new Runnable() {
    
                        @Override
                        public void run() {
                            // TODO Auto-generated method stub
                            requestLayout();
                        }
                    });
                    return true;
                }
                break;
            default:
                break;
            }
            return super.onTouchEvent(event);
        }
    }
  • 相关阅读:
    ASP.NET MVC 3: Razor中的@:和语法
    如何设置VS的代码智能提示
    七次
    不知不觉
    一切一切
    什么是喜欢
    Oracle的substr函数简单用法与substring区别
    前端必读:浏览器内部工作原理(转载)
    sublime text 插件安装 Mac版的
    一个随机上翻的小效果
  • 原文地址:https://www.cnblogs.com/Couch-potato/p/3727181.html
Copyright © 2020-2023  润新知