com.library.core.view.HorizontalListView.java Source code

Java tutorial

Introduction

Here is the source code for com.library.core.view.HorizontalListView.java

Source

package com.library.core.view;

/*
 * HorizontalListView.java v1.5.1
 * 
 * Copyright (c) 2011
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 */

import java.util.LinkedList;
import java.util.Queue;

import android.content.Context;
import android.database.DataSetObserver;
import android.graphics.Rect;
import android.os.SystemClock;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.GestureDetector.OnGestureListener;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.Scroller;

public class HorizontalListView extends AdapterView<ListAdapter> {

    /**
     * Regular layout - usually an unsolicited layout from the view system
     */
    static final int LAYOUT_NORMAL = 0;

    /**
     * Make a mSelectedItem appear in a specific location and build the rest of
     * the views from there. The top is specified by mSpecificTop.
     */
    static final int LAYOUT_SPECIFIC = 4;

    /**
     * Layout to sync as a result of a data change. Restore mSyncPosition to
     * have its top at mSpecificTop
     */
    static final int LAYOUT_SYNC = 5;

    /**
     * Controls how the next layout will happen
     */
    int mLayoutMode = LAYOUT_NORMAL;

    public boolean mAlwaysOverrideTouch = true;

    protected ListAdapter mAdapter;
    protected Scroller mScroller;
    private GestureDetector mGesture;

    private int mLeftViewIndex = -1;
    private int mRightViewIndex = 0;

    private int mMaxX = Integer.MAX_VALUE;
    private int mMinX = 0;// Integer.MIN_VALUE;
    protected int mCurrentX;
    protected int mNextX;
    private int mDisplayOffset = 0;

    private Queue<View> mRemovedViewQueue = new LinkedList<View>();

    private OnItemSelectedListener mOnItemSelected;
    private OnItemClickListener mOnItemClicked;
    private OnItemLongClickListener mOnItemLongClicked;

    private boolean mDataChanged = false;

    private int mFirstPosition = 0;
    private ViewPager pager;
    private ListView mListView;

    private Context mContext;

    public HorizontalListView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView();
        mContext = context;
        mGestureDetector = new GestureDetector(new YScrollDetector());
        setFadingEdgeLength(0);
    }

    private GestureDetector mGestureDetector;
    View.OnTouchListener mGestureListener;

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return super.onInterceptTouchEvent(ev) && mGestureDetector.onTouchEvent(ev);
    }

    class YScrollDetector extends SimpleOnGestureListener {
        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
            if (Math.abs(distanceY) > Math.abs(distanceX)) {
                return true;
            }
            return false;
        }
    }

    public void set(ViewPager apager, ListView listview) {
        pager = apager;
        mListView = listview;
    }

    private synchronized void initView() {
        mLeftViewIndex = -1;
        mRightViewIndex = 0;
        mDisplayOffset = 0;
        mCurrentX = 0;
        mNextX = 0;
        mFirstPosition = 0;
        mSpecificPosition = 0;
        mSpecificLeft = 0;
        mMaxX = Integer.MAX_VALUE;
        mMinX = 0;// Integer.MIN_VALUE;
        mScroller = new Scroller(getContext());
        mGesture = new GestureDetector(getContext(), mOnGesture);
        System.out.println("initView mMaxX = Integer.MAX_VALUE");
    }

    private synchronized void initViewForSpecific() {
        mLeftViewIndex = mSpecificPosition - 1;
        mRightViewIndex = mSpecificPosition + 1;
        mFirstPosition = mSpecificPosition;
        mDisplayOffset = 0;
        mCurrentX = 0;
        mNextX = 0;
        mMaxX = Integer.MAX_VALUE;
        mScroller = new Scroller(getContext());
        mGesture = new GestureDetector(getContext(), mOnGesture);
        System.out.println("initViewForSpecific mMaxX = Integer.MAX_VALUE");
    }

    @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();
        }

    };

    private int mSpecificLeft;

    private int mSpecificPosition;

    @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 int getFirstVisiblePosition() {
        return mFirstPosition;
    }

    @Override
    public void setSelection(int position) {
        setSelectionFromLeft(position, 0);
    }

    /**
     * Sets the selected item and positions the selection y pixels from the left
     * edge of the ListView. (If in touch mode, the item will not be selected
     * but it will still be positioned appropriately.)
     * 
     * @param position
     *            Index (starting at 0) of the data item to be selected.
     * @param x
     *            The distance from the left edge of the ListView (plus padding)
     *            that the item will be positioned.
     */
    public void setSelectionFromLeft(int position, int x) {
        if (mAdapter == null) {
            return;
        }

        if (!isInTouchMode()) {
            position = lookForSelectablePosition(position, true);
        }

        if (position >= 0) {
            mLayoutMode = LAYOUT_SPECIFIC;
            mSpecificPosition = position;
            mSpecificLeft = getPaddingLeft() + x;

            requestLayout();
        }
    }

    /**
     * Find a position that can be selected (i.e., is not a separator).
     * 
     * @param position
     *            The starting position to look at.
     * @param lookDown
     *            Whether to look down for other positions.
     * @return The next selectable position starting at position and then
     *         searching either up or down. Returns {@link #INVALID_POSITION} if
     *         nothing can be found.
     */
    int lookForSelectablePosition(int position, boolean lookDown) {
        final ListAdapter adapter = mAdapter;
        if (adapter == null || isInTouchMode()) {
            return INVALID_POSITION;
        }

        final int count = adapter.getCount();
        if (!adapter.areAllItemsEnabled()) {
            if (lookDown) {
                position = Math.max(0, position);
                while (position < count && !adapter.isEnabled(position)) {
                    position++;
                }
            } else {
                position = Math.min(position, count - 1);
                while (position >= 0 && !adapter.isEnabled(position)) {
                    position--;
                }
            }

            if (position < 0 || position >= count) {
                return INVALID_POSITION;
            }
            return position;
        } else {
            if (position < 0 || position >= count) {
                return INVALID_POSITION;
            }
            return position;
        }
    }

    private void addAndMeasureChild(final View child, int viewPos) {
        LayoutParams params = (LayoutParams) child.getLayoutParams();
        params = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT);

        addViewInLayout(child, viewPos, params, true);

        int heightMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY);
        int childHeightSpec = ViewGroup.getChildMeasureSpec(heightMeasureSpec, getPaddingTop() + getPaddingBottom(),
                params.height);
        int childWidthSpec = MeasureSpec.makeMeasureSpec(params.width > 0 ? params.width : 0,
                MeasureSpec.UNSPECIFIED);
        child.measure(childWidthSpec, childHeightSpec);

    }

    @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;
            mDataChanged = false;
        }

        if (mScroller.computeScrollOffset()) {
            int scrollx = mScroller.getCurrX();
            mNextX = scrollx;
        }

        if (mNextX <= mMinX) {
            mNextX = mMinX;
            mScroller.forceFinished(true);
        }
        if (mNextX >= mMaxX) {
            mNextX = mMaxX;
            mScroller.forceFinished(true);
        }
        System.out.println("mMaxX" + mMaxX);
        int dx = 0;
        switch (mLayoutMode) {
        case LAYOUT_SPECIFIC:
            dx = mSpecificLeft;
            detachAllViewsFromParent();
            initViewForSpecific();
            fillSpecific(mSpecificPosition, dx);
            positionItems(dx);

            mNextX = mSpecificLeft;
            mSpecificPosition = -1;
            mSpecificLeft = 0;
            break;
        default:
            dx = mCurrentX - mNextX;

            System.out.println("mCurrentX" + mCurrentX + " mNextX:" + mNextX + " dx :" + dx);

            removeNonVisibleItems(dx);
            fillList(dx);
            positionItems(dx);
        }

        mCurrentX = mNextX;
        mLayoutMode = LAYOUT_NORMAL;

        if (!mScroller.isFinished()) {
            post(new Runnable() {

                @Override
                public void run() {
                    requestLayout();
                }
            });

        }
    }

    public void resetPosition() {
        if (getChildAt(getChildCount() - 1).getRight() - getWidth() > 0
                || mAdapter.getCount() - mRightViewIndex > 0) {
            int mNextX = mCurrentX + getChildAt(getChildCount() - 1).getRight() - getWidth()
                    + (getChildAt(getChildCount() - 1).getWidth()
                            + getChildAt(getChildCount() - 1).getPaddingRight())
                            * (mAdapter.getCount() - mRightViewIndex);
            if (mNextX > 0) {
                this.mNextX = mNextX;
                mScroller.forceFinished(true);
                int dx = mCurrentX - mNextX;
                if (getChildAt(getChildCount() - 1).getRight() + dx < 0)
                    mCurrentX = mNextX;
                removeNonVisibleItems(dx);
                fillList(dx);
                positionItems(dx);
                mCurrentX = mNextX;
                mLayoutMode = LAYOUT_NORMAL;
            }
        }
    }

    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 fillSpecific(int position, int top) {
        View child = mAdapter.getView(position, null, null);
        if (child == null)
            return;
        addAndMeasureChild(child, -1);
        int edge = 0;
        edge = child.getRight();
        fillListRight(edge, top);
        edge = child.getLeft();
        fillListLeft(edge, top);
    }

    private void fillListRight(int rightEdge, final int dx) {

        while (rightEdge + dx < getWidth() && mRightViewIndex < mAdapter.getCount()) {

            View child = mAdapter.getView(mRightViewIndex, mRemovedViewQueue.poll(), this);
            addAndMeasureChild(child, -1);
            rightEdge += child.getMeasuredWidth();

            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, mRemovedViewQueue.poll(), this);
            addAndMeasureChild(child, 0);
            leftEdge -= child.getMeasuredWidth();
            if (mLeftViewIndex == 0) {
                mMinX = mCurrentX + leftEdge;
            }
            if (mMinX > 0) {
                mMinX = 0;
            }
            mLeftViewIndex--;
            mDisplayOffset -= child.getMeasuredWidth();
        }
        mFirstPosition = mLeftViewIndex + 1;
    }

    private void removeNonVisibleItems(final int dx) {
        View child = getChildAt(0);
        while (child != null && child.getRight() + dx <= 0) {
            mDisplayOffset += child.getMeasuredWidth();
            mRemovedViewQueue.offer(child);
            removeViewInLayout(child);
            mLeftViewIndex++;
            child = getChildAt(0);

        }

        child = getChildAt(getChildCount() - 1);
        while (child != null && child.getLeft() + dx >= getWidth()) {
            mRemovedViewQueue.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 event) {

        switch (event.getAction()) {
        case MotionEvent.ACTION_MOVE:
            if (pager != null) {
                pager.requestDisallowInterceptTouchEvent(true);
            }

            if (mListView != null) {
                mListView.requestDisallowInterceptTouchEvent(true);
            }
            break;
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_CANCEL:
            if (pager != null) {
                pager.requestDisallowInterceptTouchEvent(true);
                mListView.requestDisallowInterceptTouchEvent(true);
            }
            break;
        }
        boolean handled = super.dispatchTouchEvent(event);
        handled |= mGesture.onTouchEvent(event);
        return handled;

    }

    @Override
    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
        // TODO Auto-generated method stub
        super.requestDisallowInterceptTouchEvent(disallowIntercept);
    }

    protected boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
        synchronized (HorizontalListView.this) {
            mScroller.fling(mNextX, 0, (int) -velocityX, 0, mMinX, mMaxX, 0, 0);
        }
        requestLayout();

        return true;
    }

    protected boolean onDown(MotionEvent e) {
        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) {
            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());
        }
    };

    /**
     *  ?
     * 
     * @param i
     * @param j
     */
    public void smoothScrollToPosition1(int i, int j) {
        // TODO Auto-generated method stub
        MotionEvent e1 = MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(),
                MotionEvent.ACTION_DOWN, 89.333336f, 265.33334f, 0);
        MotionEvent e2 = MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(),
                MotionEvent.ACTION_UP, 150.0f, 238.00003f, 0);
        // Animation rInAnim = AnimationUtils.loadAnimation(mContext,
        // R.anim.anim_list);
        // HorizontalListView.this.setAnimation(rInAnim);
        int a = getWidth() / 5;
        mOnGesture.onScroll(e1, e2, a, 0);
    }

    /**
     * 1
     * 
     * @param i
     * @param j
     */
    public void smoothScrollToPosition2(int i, int j) {
        // TODO Auto-generated method stub
        MotionEvent e1 = MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(),
                MotionEvent.ACTION_DOWN, 89.333336f, 265.33334f, 0);
        MotionEvent e2 = MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(),
                MotionEvent.ACTION_UP, 150.0f, 238.00003f, 0);
        mOnGesture.onScroll(e1, e2, 1, 0);
    }
}