android水平循环滚动控件

CycleScrollView.java

package com.example.test;

import android.content.Context;
import android.graphics.Rect;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.GestureDetector.OnGestureListener;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Scroller;

@SuppressWarnings("deprecation")
public class CycleScrollView<T> extends ViewGroup implements OnGestureListener {

    static final String TAG = "CycleScrollView";
    Context mContext;

    /**
     * Scroll velocity.
     */
    public static final long SCROLL_VELOCITY = 50;

    /**
     * Scroll offset.
     */
    public static final int SCROLL_OFFSET = -1;

    /**
     * Touch delay.
     */
    public static final long TOUCH_DELAYMILLIS = 2000;

    /**
     * Fling duration.
     */
    public static final int FLING_DURATION = 2000;

    /**
     * Filing max velocity x.
     */
    public static final int MAX_VELOCITY_X = 1000;

    private GestureDetector detector;
    private Handler mHandler;
    private Scroller mScroller;

    /**
     * Callback interface adapter and OnItemClick.
     */
    private CycleScrollAdapter<T> mAdapter;
    private OnItemClickListener mOnItemClickListener;

    /**
     * Scroll index
     */
    private int mPreIndex;
    private int mCurrentIndex;
    private int mNextIndex;
    private View mCurrentView;
    private View mPreView;
    private View mNextView;

    private float mLastMotionX;

    // The reLayout is false can not invoke onLayout.
    private boolean reLayout = false;

    // If the item count more than screen that can scroll.
    private boolean canScroll = false;

    // A flag for switch current view.
    private boolean mCurrentViewAtLeft = true;

    // Fling distance.
    private int mFlingX = 0;

    private boolean isMoveAction = false;

    private int defaultItemY = 10;
        
    private int maxItemCount = 7;

    private int initItemX = 20;

    /**
     * The screen width.
     */
    private int screenWidth;

    /**
     * Item view height.
     */
    private int itemHeight;

    /**
     * Item view width.
     */
    private int itemWidth;

    /**
     * Item view layout x.
     */
    private int itemX = getInitItemX();

    /**
     * Item view layout y.
     */
    private int itemY = defaultItemY;

    // Auto scroll view task.
    private final Runnable mScrollTask = new Runnable() {

        @Override
        public void run() {
            if (canScroll) {
                scrollView(SCROLL_OFFSET);
                mHandler.postDelayed(this, SCROLL_VELOCITY);// Loop self.
            }
        }
    };

    public CycleScrollView(Context context) {
        super(context);
        onCreate(context);
    }

    public CycleScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
        onCreate(context);
    }

    public CycleScrollView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        onCreate(context);
    }

    private void onCreate(Context context) {
        mContext = context;
        detector = new GestureDetector(this);
        mHandler = new Handler();
        mScroller = new Scroller(context);
    }

    /**
     * Create scroll index.
     */
    public void createIndex() {
        if (canScroll) {
            mPreIndex = maxItemCount - 1;
            mCurrentIndex = 0;
            mNextIndex = 1;
            mPreView = getChildAt(mPreIndex);
            mCurrentView = getChildAt(mCurrentIndex);
            mNextView = getChildAt(mNextIndex);
        }
    }

    /**
     * Set item click callback.
     * 
     * @param onItemClickListener
     *            The callback
     */
    public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
        mOnItemClickListener = onItemClickListener;
    }

    /**
     * Set itemAdapter for addItem and bindItem.
     * 
     * @param itemAdapter
     */
    public void setAdapter(CycleScrollAdapter<T> adapter) {
        mAdapter = adapter;
    }

    /**
     * Start auto scroll.
     */
    public void startScroll() {
        if (canScroll) {
            mHandler.post(mScrollTask);
        }
    }

    /**
     * Stop auto scroll and filing scroll task.
     */
    public void stopScroll() {
        mHandler.removeCallbacks(mScrollTask);
    }

    /**
     * Delay start auto scroll task.
     */
    public void delayStartScroll() {
        if (canScroll) {
            mHandler.postDelayed(mScrollTask, TOUCH_DELAYMILLIS);
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int count = this.getChildCount();
        for (int i = 0; i < count; i++) {
            View child = this.getChildAt(i);
            child.measure(widthMeasureSpec, heightMeasureSpec);
        }
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        /**
         * On layout set the child view layout by x y width and height.
         */
        if (reLayout) {// Run one times.
            for (int i = 0; i < getChildCount(); i++) {
                View child = this.getChildAt(i);
                child.setVisibility(View.VISIBLE);
                child.layout(itemX, getItemY(), itemX + getItemWidth(),
                        getItemY() + getItemHeight());
                itemX += getItemMargin();
            }
            reLayout = !reLayout;
        }
    }

    /**
     * When fling view run the fling task scroll view.
     */
    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
            float velocityY) {

        if (e1 == null || e2 == null) {
            return false;
        }

        // When deltaX and velocityX not good return false.
        if (Math.abs(velocityX) < MAX_VELOCITY_X) {
            return false;
        }

        // Get the delta x.
        float deltaX = (e1.getX() - e2.getX());

        /**
         * If can fling stop other scroll task at first , delay the task after
         * fling.
         */
        mHandler.removeCallbacks(mScrollTask);
        if (canScroll) {
            mHandler.postDelayed(mScrollTask, TOUCH_DELAYMILLIS
                    + FLING_DURATION - 1000);
        }

        /**
         * The flingX is fling distance.
         */
        mFlingX = (int) deltaX;

        // Start scroll with fling x.
        mScroller.startScroll(0, 0, mFlingX, 0, FLING_DURATION);
        return false;
    }

    @Override
    public void computeScroll() {
        if (canScroll && mScroller.computeScrollOffset()) {
            /**
             * The Scroller.getCurrX() approach mFlingX , the deltaX more and
             * more small.
             */
            int deltaX = mFlingX - mScroller.getCurrX();
            scrollView(-deltaX / 10);
            postInvalidate();
        }
    }

    /**
     * When touch event is move scroll child view.
     */
    @Override
    public boolean onTouchEvent(MotionEvent ev) {

        // Get event x,y at parent view.
        final float x = ev.getX();

        /**
         * Get event x,y at screen.
         */
        final int rawX = (int) ev.getRawX();
        final int rawY = (int) ev.getRawY();

        switch (ev.getAction()) {
        case MotionEvent.ACTION_DOWN:
            // Reset isMoveAction.
            isMoveAction = false;
            // Get motionX.
            mLastMotionX = x;
            break;
        case MotionEvent.ACTION_MOVE:
            // When action move set isMoveAction true.
            isMoveAction = true;
            // Only support one pointer.
            if (ev.getPointerCount() == 1) {
                // Compute delta X.
                int deltaX = 0;
                deltaX = (int) (x - mLastMotionX);
                mLastMotionX = x;
                // When canScroll is true, scrollView width deltaX.
                if (canScroll) {
                    scrollView(deltaX);
                }
            }
            break;
        case MotionEvent.ACTION_UP:
            /**
             * If not move find click item and invoke click event.
             */
            if (!isMoveAction) {
                View view = getClickItem(rawX, rawY);
                if (view != null) {
                    mOnItemClickListener.onItemClick(Integer.valueOf(view
                            .getTag().toString()));
                }
            }
            break;
        }
        return this.detector.onTouchEvent(ev);
    }

    /**
     * Get click item view by rawX and rawY.
     * @param rawX the x at screen.
     * @param rawY the y at screen.
     * @return the click item view.
     */
    private View getClickItem(final int rawX, final int rawY) {
        for (int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);
            // Get item view rect.
            Rect rect = new Rect();
            child.getGlobalVisibleRect(rect);
            // If click point on the item view, invoke the click event.
            if (rect.contains(rawX, rawY)) {
                return child;
            }
        }
        return null;
    }

    /**
     * Scroll view by delta x.
     * 
     * @param deltaX
     *            The scroll distance.
     */
    private void scrollView(int deltaX) {
        // Move child view by deltaX.
        moveChildView(deltaX);
        // After move change index.
        if (deltaX < 0) {// move left
            // If current at right switch current view to left.
            switchCurrentViewToLeft();
            // change previous current next index.
            moveToNext();
        } else {// move right
            // If current at left switch current view to right.
            switchCurrentViewToRight();
            // change previous current next index.
            moveToPre();
        }
        invalidate();
    }

    /**
     * Move view by delta x.
     * 
     * @param deltaX
     *            The move distance.
     */
    private void moveChildView(int deltaX) {
        for (int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);
            child.layout(child.getLeft() + deltaX, child.getTop(),
                    child.getRight() + deltaX, child.getBottom());
        }
    }

    /**
     * Current event is move to left, if current view at right switch current
     * view to left.
     */
    private void switchCurrentViewToLeft() {
        if (!mCurrentViewAtLeft) {
            mPreIndex = mCurrentIndex;
            mCurrentIndex = mNextIndex;
            mNextIndex++;
            if (mNextIndex > maxItemCount - 1) {
                mNextIndex = 0;
            }
            mCurrentView = getChildAt(mCurrentIndex);
            mPreView = getChildAt(mPreIndex);
            mNextView = getChildAt(mNextIndex);
            mCurrentViewAtLeft = !mCurrentViewAtLeft;
        }
    }

    /**
     * Current event is move to right, if current view at left switch current
     * view to right.
     */
    private void switchCurrentViewToRight() {
        if (mCurrentViewAtLeft) {
            mNextIndex = mCurrentIndex;
            mCurrentIndex = mPreIndex;
            mPreIndex--;
            if (mPreIndex < 0) {
                mPreIndex = maxItemCount - 1;
            }
            mCurrentView = getChildAt(mCurrentIndex);
            mPreView = getChildAt(mPreIndex);
            mNextView = getChildAt(mNextIndex);
            mCurrentViewAtLeft = !mCurrentViewAtLeft;
        }
    }

    /**
     * Current event is move to left,if current view move out of screen move the
     * current view to right and reBind the item change index.
     */
    private void moveToNext() {
        if (mCurrentView.getRight() < 0) {
            mCurrentView.layout(mPreView.getLeft() + getItemMargin(),
                    getItemY(), mPreView.getLeft() + getItemMargin()
                            + getItemWidth(), getItemY() + getItemHeight());

            if (mCurrentView.getTag() != null) {
                int listIndex = (Integer) mCurrentView.getTag();
                int index = (listIndex + maxItemCount) % mAdapter.getCount();
                mAdapter.bindView(mCurrentView, mAdapter.get(index));
                mCurrentView.setTag(index);
            }

            mPreIndex = mCurrentIndex;
            mCurrentIndex = mNextIndex;
            mNextIndex++;
            if (mNextIndex > maxItemCount - 1) {
                mNextIndex = 0;
            }
            mCurrentView = getChildAt(mCurrentIndex);
            mPreView = getChildAt(mPreIndex);
            mNextView = getChildAt(mNextIndex);
            moveToNext();
        }
    }

    /**
     * Current event is move to right,if current view move out of screen move
     * the current view to left and reBind the item change index.
     */
    private void moveToPre() {
        if (mCurrentView.getLeft() > getScreenWidth()) {
            mCurrentView.layout(mNextView.getLeft() - getItemMargin(),
                    getItemY(), mNextView.getLeft() - getItemMargin()
                            + getItemWidth(), getItemY() + getItemHeight());

            if (mCurrentView.getTag() != null) {
                int listIndex = (Integer) mCurrentView.getTag();
                int index = (listIndex - maxItemCount + mAdapter.getCount())
                        % mAdapter.getCount();
                mAdapter.bindView(mCurrentView, mAdapter.get(index));
                mCurrentView.setTag(index);
            }

            mNextIndex = mCurrentIndex;
            mCurrentIndex = mPreIndex;
            mPreIndex--;
            if (mPreIndex < 0) {
                mPreIndex = maxItemCount - 1;
            }
            mCurrentView = getChildAt(mCurrentIndex);
            mPreView = getChildAt(mPreIndex);
            mNextView = getChildAt(mNextIndex);
            moveToPre();
        }
    }

    @Override
    public boolean onDown(MotionEvent e) {
        return true;
    }

    @Override
    public void onShowPress(MotionEvent e) {
    }

    @Override
    public boolean onSingleTapUp(MotionEvent e) {
        return false;
    }

    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
            float distanceY) {
        return false;
    }

    @Override
    public void onLongPress(MotionEvent e) {
    }

    public int getMaxItemCount() {
        return maxItemCount;
    }

    public void setMaxItemCount(int maxItemCount) {
        this.maxItemCount = maxItemCount;
    }

    public void setReLayout(boolean reLayout) {
        this.reLayout = reLayout;
    }

    public void setCanScroll(boolean canScroll) {
        this.canScroll = canScroll;
    }

    public int getItemX() {
        return itemX;
    }

    public void setItemX(int itemX) {
        this.itemX = itemX;
    }

    public int getItemY() {
        return itemY;
    }

    public void setItemY(int itemY) {
        this.itemY = itemY;
    }

    public int getItemWidth() {
        return itemWidth;
    }

    public void setItemWidth(int itemWidth) {
        this.itemWidth = itemWidth;
    }

    public int getItemHeight() {
        return itemHeight;
    }

    public void setItemHeight(int itemHeight) {
        this.itemHeight = itemHeight;
    }

    public int getItemMargin() {
        return (screenWidth - itemWidth * (maxItemCount - 1) - initItemX * 2)/(maxItemCount - 2) + itemWidth;
    }

    public int getScreenWidth() {
        return screenWidth;
    }

    public void setScreenWidth(int screenWidth) {
        this.screenWidth = screenWidth;
    }

    public int getInitItemX() {
        return initItemX;
    }

    public void setInitItemX(int initItemX) {
        this.initItemX = initItemX;
    }

    /**
     * The interface for item click callback.
     */
    interface OnItemClickListener {
        public boolean onItemClick(int position);
    }

}


CycleScrollAdapter.java

package com.example.test;

import java.util.List;

import android.app.Activity;
import android.content.Context;
import android.util.DisplayMetrics;
import android.view.View;

public abstract class CycleScrollAdapter<T> {

    private List<T> list;
    private CycleScrollView<T> mCycleScrollView;
    Context mContext;

    /**
     * Initial CycleScrollAdapter bind list to view.
     * 
     * @param list
     *            The list data.
     * @param cycleScrollView
     *            The CycleScrollView.
     * @param context
     *            The Context.
     */
    public CycleScrollAdapter(List<T> list, CycleScrollView<T> cycleScrollView,
            Context context) {
        this.list = list;
        mContext = context;
        mCycleScrollView = cycleScrollView;
        mCycleScrollView.setAdapter(this);
        GetScreenWidthPixels();
        initView(list);
    }

    /**
     * Get screen width pixels.
     */
    private void GetScreenWidthPixels() {
        DisplayMetrics dm = new DisplayMetrics();  
        Activity a = (Activity) mContext;
        a.getWindowManager().getDefaultDisplay().getMetrics(dm);  
        mCycleScrollView.setScreenWidth(dm.widthPixels);
    }

    /**
     * Bind list to view.
     * 
     * @param list
     *            The list data.
     */
    protected void initView(List<T> list) {
        if (list == null || list.size() == 0) {
            return;
        }
        
        // Clear all view from ViewGroup at first.
        mCycleScrollView.removeAllViewsInLayout();

        // Loop list.
        for (int i = 0; i < list.size(); i++) {
            /**
             * If list size more than MaxItemCount break the loop, only create
             * view count is MaxItemCount.
             */
            if (i == mCycleScrollView.getMaxItemCount()) {
                break;
            }

            /**
             * If list size less than MaxItemCount at the last loop reLayout
             * otherwise at the MaxItemCount index reLayout.
             */
            if (i == list.size() - 1
                    || i == mCycleScrollView.getMaxItemCount() - 1) {
                mCycleScrollView.setItemX(mCycleScrollView.getInitItemX());
                mCycleScrollView.setReLayout(true);
            }
            add(list.get(i), i);
        }

        /**
         * If list count more than MaxItemCount the view can scroll otherwise
         * can not scroll.
         */
        if (list.size() >= mCycleScrollView.getMaxItemCount()) {
            mCycleScrollView.setCanScroll(true);
        } else {
            mCycleScrollView.setCanScroll(false);
        }

        /**
         * If list count more than MaxItemCount reBuild index.
         */
        mCycleScrollView.createIndex();
    }

    /**
     * Get list size.
     * 
     * @return The list size.
     */
    public int getCount() {
        return list.size();
    }

    /**
     * Returns the element at the specified location in this
     * 
     * @param index
     *            the index of the element to return.
     * @return the element at the specified location.
     */
    public T get(int index) {
        return list.get(index);
    }

    /**
     * Adds the specified object at the end of this and refresh view.
     * 
     * @param t
     *            the object to add.
     */
    public void addItem(T t) {
        list.add(t);
        initView(list);
    }

    /**
     * Removes the first occurrence of the specified object from this and
     * refresh view.
     * 
     * @param t
     *            the object to remove.
     */
    public void removeItem(T t) {
        list.remove(t);
        initView(list);
    }

    /**
     * Add the specified view to the index.
     * 
     * @param t
     *            The data to add.
     * @param index
     *            the index.
     */
    private void add(T t, int index) {
        View view = getView(t);
        ComputeItemSize(view);
        mCycleScrollView.addView(view);
        view.setTag(index);
    }

    /**
     * If item size is null compute item size.
     * 
     * @param view
     *            the item view.
     */
    private void ComputeItemSize(View view) {
        if (mCycleScrollView.getItemWidth() == 0
                || mCycleScrollView.getItemHeight() == 0) {
            int w = View.MeasureSpec.makeMeasureSpec(0,
                    View.MeasureSpec.UNSPECIFIED);
            int h = View.MeasureSpec.makeMeasureSpec(0,
                    View.MeasureSpec.UNSPECIFIED);
            view.measure(w, h);
            int height = view.getMeasuredHeight();
            int width = view.getMeasuredWidth();
            mCycleScrollView.setItemHeight(height);
            mCycleScrollView.setItemWidth(width);
        }
    }

    /**
     * Get item view.
     * 
     * @param t
     *            the data need bind to view.
     * @return the view.
     */
    public abstract View getView(T t);

    /**
     * Bind the item to view.
     * 
     * @param child
     *            the item view need bind.
     * @param t
     *            the item.
     */
    public abstract void bindView(View child, T t);
}

以上两个是核心类,下面是测试代码。

实现CycleScrollAdapter

AppCycleScrollAdapter.java绑定视图和应用数据

package com.example.test;

import java.util.List;

import android.content.Context;
import android.content.pm.PackageInfo;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;

public class AppCycleScrollAdapter extends CycleScrollAdapter<PackageInfo> {
    
    public AppCycleScrollAdapter(List<PackageInfo> list,
            CycleScrollView<PackageInfo> cycleScrollView, Context context) {
        super(list, cycleScrollView, context);
    }
    
    @Override
    protected void initView(List<PackageInfo> list) {
        super.initView(list);
    }

    @Override
    public void bindView(View child, PackageInfo pi) {
        ImageView image = (ImageView) child.findViewById(R.id.item_image);
        TextView text = (TextView) child.findViewById(R.id.item_text);
        image.setImageDrawable(pi.applicationInfo.loadIcon(mContext
                .getPackageManager()));
        text.setText(pi.applicationInfo.loadLabel(mContext.getPackageManager()));  
    }

    @Override
    public View getView(PackageInfo pi) {
        View view = View.inflate(mContext, R.layout.view_item, null);
        // inflate APP icon view
        ImageView image = (ImageView) view.findViewById(R.id.item_image);
        // inflate APP name view
        TextView text = (TextView) view.findViewById(R.id.item_text);
        image.setImageDrawable(pi.applicationInfo.loadIcon(mContext
                .getPackageManager()));
        text.setText(pi.applicationInfo.loadLabel(mContext.getPackageManager()));
        return view;
    }
}


入口Activity

package com.example.test;


import java.util.List;


import android.app.Activity;
import android.content.pm.PackageInfo;
import android.os.Bundle;
import android.view.Menu;

public class MainActivity extends Activity{
   
    private CycleScrollView<PackageInfo> mCycleScrollView;
    private AppCycleScrollAdapter mAdapter;
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        mCycleScrollView = ((CycleScrollView<PackageInfo>) this.findViewById(R.id.cycle_scroll_view));

        /**
         * Get APP list and sort by update time.
         */
        List<PackageInfo> list = this.getPackageManager()
                .getInstalledPackages(0);

        mAdapter = new AppCycleScrollAdapter(list, mCycleScrollView, this);
        
    }
    


    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.activity_main, menu);
        return true;
    }

}


布局文件

<?xml version="1.0" encoding="utf-8"?>
<AbsoluteLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    >

    <ImageView
        android:id="@+id/item_image"
        android:layout_width="60dip"
        android:layout_height="60dip"
        android:layout_y="5dip"
        android:layout_x="10dip"
         />

    <TextView
        android:id="@+id/item_text"
        android:layout_width="80dip"
        android:layout_height="20dip"
        android:layout_y="65dip"
        android:layout_x="0dip"
        android:gravity="center_horizontal" />

</AbsoluteLayout>
<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" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:text="@string/hello_world"
        tools:context=".MainActivity" />
    
		<com.example.test.CycleScrollView
        android:id="@+id/cycle_scroll_view"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:background="#B9000000"
        />

</RelativeLayout>



原文地址:https://www.cnblogs.com/riskyer/p/3306400.html