橫向ListView(五) —— 實現左/右拉回彈功能及事件觸發

這篇文章將介紹在之前版本的橫向ListView中添加左/右拉伸及回彈功能(採用阻尼式算法)。

之前的代碼存在許多bug,這個版本處理添加功能以外,還修補了之前版本的bug,在代碼中都有相應的註解。

 

實現思路如下:

    1.在原來的版本上擴展滾動的邊界值

        在之前的版本代碼中,列表的滾動無法超出界面的可視區域,爲了實現左/右拉伸及回彈功能,必須擴展其滾動邊界值(在代碼中對應的屬性:maxScrollValue、minScrollValue);還必須考慮兼容以前的版本功能。

    2.在OnGestureListener.onScroll()方法中採用阻尼算法重新計算滾動值

    3.onTouchEvent()中採用事件攔截,實現回彈功能

    4.居中顯示功能和左/右拉伸功能不兼容,做好排異處理

    5.爲左/右拉伸功能添加回調事件,同時設置啓動開關

 

先上完整代碼(ScrollBar.java的源碼請參考文章:《橫向ListView(四) —— 添加滾動條》):

package com.hss.os.horizontallistview.history_version;

import android.content.Context;
import android.database.DataSetObserver;
import android.graphics.Rect;
import android.os.Build;
import android.support.annotation.RequiresApi;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListAdapter;
import android.widget.Scroller;

import com.hss.os.horizontallistview.ScrollBar;

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

/**
 * Created by sxyx on 2017/7/6.
 */

public class HorizontalListView5 extends AdapterView<ListAdapter> {

    private Queue<View> cacheView = new LinkedList<>();//列表項緩存視圖
    private ListAdapter adapter = null;
    private GestureDetector mGesture;

    private int firstItemIndex = 0;//顯示的第一個子項的下標
    private int lastItemIndex = -1;//顯示的最後的一個子項的下標
    private int scrollValue=0;//列表已經發生有效滾動的位移值
    private int hasToScrollValue=0;//接下來列表發生滾動所要達到的位移值
    private int maxScrollValue=Integer.MAX_VALUE;//列表發生滾動所能達到的最大位移值(這個由最後顯示的列表項決定)
    private int minScrollValue=Integer.MIN_VALUE;//列表發生滾動所能達到的最小位移值(值爲0表示不能發生類似下拉刷新的操作,負值表示可以)
    private int displayOffset=0;//列表顯示的偏移值(用於矯正列表顯示的所有子項的顯示位置)

    private Scroller mScroller;
    private int firstItemLeftEdge=0;//第一個子項的左邊界
    private int lastItemRightEdge=0;//最後一個子項的右邊界

    private View headView;
    private View footView;
    private boolean hasHeadView=false;
    private boolean hasFootView=false;
    private boolean canShowInMid=false;

    private ScrollBar mScrollBar;
    private int headViewWidth=0;//頭視圖寬度
    private int footViewWidth=0;//尾視圖寬度
    private boolean canShowScrollBar=true;//是否需要顯示滾動條

    private boolean canPullUp=true;//可以左拉
    private boolean canPullDown=true;//可以右拉


    public HorizontalListView5(Context context) {
        super(context);
        init(context);
    }

    public HorizontalListView5(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public HorizontalListView5(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public HorizontalListView5(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init(context);
    }

    private void init(Context context){
        mGesture = new GestureDetector(getContext(), mOnGesture);
        mScroller=new Scroller(context);
        mScrollBar=new ScrollBar(context);
    }



    private void initParams(){
        mScroller.forceFinished(true);//避免在滑動過程中變換視圖內容時,出現列表無法滾動的情況
        removeAllViewsInLayout();
        if(adapter!=null&&lastItemIndex<adapter.getCount())
            hasToScrollValue=scrollValue;//保持顯示位置不變
        else hasToScrollValue=0;//滾動到列表頭
        scrollValue=0;//列表已經發生有效滾動的位移值
        firstItemIndex = 0;//顯示的第一個子項的下標
        lastItemIndex = -1;//顯示的最後的一個子項的下標
        maxScrollValue=Integer.MAX_VALUE;//列表發生滾動所能達到的最大位移值(這個由最後顯示的列表項決定)
        // 列表發生滾動所能達到的最小位移值(值爲0表示不能發生類似下拉刷新的操作,負值表示可以)
        if(!isCanPullLeft()) minScrollValue = 0;
        else  minScrollValue = Integer.MIN_VALUE;
        displayOffset=0;//列表顯示的偏移值(用於矯正列表顯示的所有子項的顯示位置)
        firstItemLeftEdge=0;//第一個子項的左邊界
        lastItemRightEdge=0;//最後一個子項的右邊界
        if(hasHeadView||hasFootView) {
            if (hasHeadView) {
                scrollValue = headView.getMeasuredWidth();
                headView.layout(0, 0, 0, 0);
                setHeadView(headView);
            }
            if (hasFootView) {
                footView.layout(0, 0, 0, 0);
                setFootView(footView);
            }
        }else  requestLayout();
    }


    private DataSetObserver mDataObserver = new DataSetObserver() {

        @Override
        public void onChanged() {
            //執行Adapter數據改變時的邏輯
            initParams();
        }

        @Override
        public void onInvalidated() {
            //執行Adapter數據失效時的邏輯
            initParams();
        }

    };

    @Override
    public ListAdapter getAdapter() {
        return adapter;
    }

    @Override
    public void setAdapter(ListAdapter adapter) {
        if(adapter!=null){
            adapter.registerDataSetObserver(mDataObserver);
        }
        if(this.adapter!=null){
            this.adapter.unregisterDataSetObserver(mDataObserver);
        }
        this.adapter=adapter;
        requestLayout();
    }

    @Override
    public View getSelectedView() {
        return null;
    }

    @Override
    public void setSelection(int i) {

    }

    private void addAndMeasureChild(View child, int viewIndex) {
        LayoutParams params = child.getLayoutParams();
        params = params==null ? new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT):params;

        addViewInLayout(child, viewIndex, params, true);
        child.measure(MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.UNSPECIFIED),
                MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.UNSPECIFIED));
    }



    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);

        //在執行佈局之前需要先移除滾動條,以免影響其它視圖的顯示運算
        mScrollBar.remove(this);

        //需要先佈局列表項再根據餘下的空間佈局列表頭尾
        //佈局列表項
        /*
        1.計算這一次整體滾動偏移量
        2.根據偏移量提取需要緩存視圖
        3.根據偏移量顯示新的列表項
        4.根據整體偏移值整頓所有列表項位置
        5.計算最大滾動位移值,記錄已經發生有效滾動的位移值
        6.根據顯示的最終效果,判斷是否要居中顯示
         */

        int dx=calculateScrollValue();
        removeNonVisibleItems(dx);
        showListItem(dx);
        adjustItems();
        //佈局列表頭、尾
        adjustHeadAndFootView(dx);
        calculateMaxScrollValue();
        adjustShow();

        if(canShowScrollBar) {
            //佈局完所有的視圖之後添加上滾動條的顯示
            addAndMeasureChild(mScrollBar, getChildCount());
            if (adapter != null) {
                mScrollBar.showHorizontal(this, firstItemIndex, lastItemIndex, adapter.getCount(), headViewWidth, footViewWidth);
            } else {
                mScrollBar.showHorizontal(this, 0, 0, 0, headViewWidth, footViewWidth);
            }
        }

        //繼續滾動
        if(!mScroller.isFinished()){
            //在onLayout中requestLayout()方法必須按以下形式調用,否則沒有效果
            post(new Runnable(){
                @Override
                public void run() {
                    requestLayout();
                }
            });
        }else if(isRemoveFootView){//表示所有的計算都已經完成
            isRemoveFootView=false;
            //這是修補因移除尾視圖而出現列表頭空白的bug
            int startIndex=0;
            if(canShowScrollBar) startIndex=1;
            if(getChildCount()>startIndex){
                if(getChildAt(0).getLeft()>getShowStartEdge()){
                    //表示列表頭有出現空白
                    hasToScrollValue += getChildAt(0).getLeft()-getShowStartEdge();
                    //在onLayout中requestLayout()方法必須按以下形式調用,否則沒有效果
                    post(new Runnable(){
                        @Override
                        public void run() {
                            requestLayout();
                        }
                    });
                }
            }
        }
    }

    /**
     * 計算這一次整體滾動偏移量
     * @return
     */
    private int calculateScrollValue(){
        int dx=0;

        if(mScroller.computeScrollOffset()){
            hasToScrollValue = mScroller.getCurrX();
        }

        if(hasToScrollValue <= minScrollValue){
            hasToScrollValue = minScrollValue;
            mScroller.forceFinished(true);
        }

        if(hasToScrollValue >= maxScrollValue) {
            hasToScrollValue = maxScrollValue;
            mScroller.forceFinished(true);
        }

        dx=hasToScrollValue-scrollValue;
        scrollValue=hasToScrollValue;

        return -dx;
    }

    /**
     * 計算最大滾動值
     */
    private void calculateMaxScrollValue(){

        if(getListItemCount()>0) {
            if(lastItemIndex==adapter.getCount()-1) {//已經顯示了最後一項
                if(getChildAt(getChildCount() - 1).getRight()>=getShowEndEdge()) {
                    setMaxScrollValue(scrollValue + getChildAt(getChildCount() - 1).getRight() - getShowEndEdge());
                }else{
                    setMaxScrollValue(0);
                }
            }else{
                setMaxScrollValue(Integer.MAX_VALUE);
            }
        }else{
            if(adapter!=null&&adapter.getCount()>0){
                // 修補bug:當尾視圖足夠寬能夠佔據所有可視區域時,
                // maxScrollValue的值沒有改變,而導致滾動出現空白區域
                setMaxScrollValue(scrollValue + getChildAt(getChildCount() - 1).getRight() - getShowEndEdge());
            }else {
                if (getChildCount() > 0
                        && getChildAt(getChildCount() - 1).getRight() >= getShowEndEdge()) {
                    setMaxScrollValue(scrollValue + getChildAt(getChildCount() - 1).getRight() - getShowEndEdge());
                }else{
                    setMaxScrollValue(0);
                }
            }
        }
    }

    /**
     * 根據偏移量提取需要緩存視圖
     * @param dx
     */
    private void removeNonVisibleItems(int dx) {
        if(getListItemCount()>0) {
            //移除列表頭
            View child = getChildAt(getStartItemIndex());
            while (getListItemCount()>0&&child != null && child.getRight() + dx <= getShowStartEdge()) {
                displayOffset += child.getMeasuredWidth();
                cacheView.offer(child);
                removeViewInLayout(child);
                firstItemIndex++;
                child = getChildAt(getStartItemIndex());
            }

            //移除列表尾
            child = getChildAt(getEndItemIndex());
            while (getListItemCount()>0&&child != null && child.getLeft() + dx >= getShowEndEdge()) {
                cacheView.offer(child);
                removeViewInLayout(child);
                lastItemIndex--;
                child = getChildAt(getEndItemIndex());
            }
        }
    }

    /**
     * 根據偏移量顯示新的列表項
     * @param dx
     */
    private void showListItem(int dx) {
        if(adapter==null)return;

        int firstItemEdge = getFirstItemLeftEdge()+dx;
        int lastItemEdge = getLastItemRightEdge()+dx;
        displayOffset+=dx;//計算偏移量
        //顯示列表頭視圖
        while(firstItemEdge > getShowStartEdge() && firstItemIndex-1 >= 0) {
            firstItemIndex--;//往前顯示一個列表項
            View child = adapter.getView(firstItemIndex, cacheView.poll(), this);
            addAndMeasureChild(child, getStartItemIndex());
            firstItemEdge -= child.getMeasuredWidth();
            displayOffset -= child.getMeasuredWidth();
        }
        //顯示列表未視圖
        while(lastItemEdge < getShowEndEdge() && lastItemIndex+1 < adapter.getCount()) {
            lastItemIndex++;//往後顯示一個列表項
            View child = adapter.getView(lastItemIndex, cacheView.poll(), this);
            addAndMeasureChild(child, getEndItemIndex()+1);
            lastItemEdge += child.getMeasuredWidth();
        }
    }

    /**
     * 調整各個item的位置
     */
    private void adjustItems() {
        if(getListItemCount() > 0){
            int left = displayOffset+getShowStartEdge();
            int top = getPaddingTop();
            int endIndex = getEndItemIndex();
            int startIndex = getStartItemIndex();

            int childWidth,childHeight;
            for(int i=startIndex;i<=endIndex;i++){
                View child = getChildAt(i);
                childWidth = child.getMeasuredWidth();
                childHeight = child.getMeasuredHeight();
                child.layout(left, top, left + childWidth, top + childHeight);
                left += childWidth;
            }

            firstItemLeftEdge=getChildAt(getStartItemIndex()).getLeft();
            lastItemRightEdge=getChildAt(getEndItemIndex()).getRight();
        }
    }

    /**
     * 調整列表頭、尾
     */
    private void adjustHeadAndFootView(int dx){
        headViewWidth=footViewWidth=0;
        if(hasHeadView){
            int left,right;
            if(getListItemCount()>0){
                right=firstItemLeftEdge;
            }else{
                right=headView.getRight()+dx;
            }
            left=right-headView.getMeasuredWidth();
            headView.layout(left, getPaddingTop(), right, headView.getMeasuredHeight()+getPaddingTop());
            headViewWidth=headView.getMeasuredWidth();
        }

        if(hasFootView){
            int left,right;
            if(getListItemCount()>0){
                left=getChildAt(getEndItemIndex()).getRight();
            }else{
                //添加headView.getRight()>=getShowStartEdge()條件用於修補原先的bug
                if(hasHeadView&&headView.getRight()>=getShowStartEdge())
                    left=headView.getRight();
                else {
                    if(footView.getLeft()==0&&dx==0){//第一次賦值
                        left=getShowStartEdge();
                    }else{
                        left=footView.getLeft()+dx;
                    }
                    //調整以修補顯示bug
                    if(hasHeadView) {//重新調整headView的位置
                        headView.layout(left-headView.getMeasuredWidth(), getPaddingTop(), left, headView.getMeasuredHeight() + getPaddingTop());
                    }
                }
            }
            right=left+footView.getMeasuredWidth();
            footView.layout(left, getPaddingTop(), right, footView.getMeasuredHeight()+getPaddingTop());
            footViewWidth=footView.getMeasuredWidth();
        }

    }

    private void adjustShow(){
        if(isCanShowInMid()){//可以居中顯示
            int endEdge=getShowEndEdge();
            boolean canAdjust=false;
            if(hasFootView){
                if(footView.getRight()<endEdge) canAdjust=true;
            }else if(getListItemCount()>0){
                if(getChildAt(getEndItemIndex()).getRight()<endEdge) canAdjust=true;
            }else if(hasHeadView){
                if(headView.getRight()<endEdge) canAdjust=true;
            }
            if(canAdjust){
                //居中顯示
                int itemsWidth=getChildAt(getChildCount()-1).getRight()-getShowStartEdge();
                int left=(getShowWidth()-itemsWidth)/2+getShowStartEdge();
                int right;
                View child;
                for(int i=0;i<getChildCount();i++){
                    child= getChildAt(i);
                    right=left+child.getMeasuredWidth();
                    child.layout(left,child.getTop(),right,child.getBottom());
                    left=right;
                }
            }

        }
    }





    //以下八個方法爲概念性封裝方法,有助於往後的擴展和維護


    /**
     * 獲得列表視圖中item View的總數
     * @return
     */
    private int getListItemCount(){
        int itemCount=getChildCount();
        if(hasHeadView)itemCount-=1;
        if(hasFootView)itemCount-=1;
        return itemCount;
    }
    /**
     * 獲得列表視圖中第一個item View下標
     * @return
     */
    private int getStartItemIndex(){
        if(hasHeadView) return 1;
        return 0;
    }
    /**
     * 獲得列表視圖中最後一個item View下標
     * @return
     */
    private int getEndItemIndex(){
        if(hasFootView) return getChildCount()-2;
        return getChildCount()-1;
    }
    /**
     * 獲得列表視圖中第一個item View左邊界值
     * @return
     */
    private int getFirstItemLeftEdge(){
        if(getListItemCount()>0) {
            return firstItemLeftEdge;
        }else{
            if(hasHeadView) return headView.getRight();
            else return getShowStartEdge();
        }
    }
    /**
     * 獲得列表視圖中最後一個item View右邊界值
     * @return
     */
    private int getLastItemRightEdge(){
        if(getListItemCount()>0) {
            return lastItemRightEdge;
        }else{
            if(hasFootView) return footView.getLeft();
            else return getShowStartEdge();
        }
    }
    /**
     * 取得視圖可見區域的左邊界
     * @return
     */
    private int getShowStartEdge(){
        return getPaddingLeft();
    }
    /**
     * 取得視圖可見區域的右邊界
     * @return
     */
    private int getShowEndEdge(){
        return getWidth()-getPaddingRight();
    }
    /**
     * 取得視圖可見區域的寬度
     * @return
     */
    private int getShowWidth(){
        return getWidth()-getPaddingLeft()-getPaddingRight();
    }


    public void setMaxScrollValue(int maxScrollValue) {
        if(isCanPullRight()&&maxScrollValue!=Integer.MAX_VALUE) {
            if(isSpringBack){//在執行回彈操作
                this.maxScrollValue = Integer.MAX_VALUE;
            }else if(mScroller.isFinished()){//手動執行滾動操作
                this.maxScrollValue = Integer.MAX_VALUE;
            }else{
                if(maxScrollValue<0)this.maxScrollValue=0;
                else this.maxScrollValue = maxScrollValue;
            }
        }else{
            if(maxScrollValue<0)this.maxScrollValue=0;
            else this.maxScrollValue = maxScrollValue;
        }
    }







    public void setHeadView(View view){
        if(view!=null) {
            int headRight=-1;
            int width=0;
            if (hasHeadView&&headView!=null) {
                headRight=headView.getRight();
                width=headView.getWidth();
                removeViewInLayout(headView);
            }
            hasHeadView = true;
            headView=view;
            addAndMeasureChild(headView, 0);

            if(getListItemCount()>0) {//有列表內容
                if (headRight == -1) {
                    //新增列表頭
                    if (firstItemIndex == 0) {//第一個顯示的是第一個列表項
                        //滾動整個列表,讓其顯示完整列表頭(讓列表往回滾)
                        scrollValue = headView.getMeasuredWidth()
                                + getShowStartEdge() - firstItemLeftEdge;
                        hasToScrollValue=0;
                    } else {//不是顯示第一個列表項
                        //不滾動列表項,增加歷史滾動值
                        hasToScrollValue += headView.getMeasuredWidth();
                        scrollValue = hasToScrollValue;
                    }
                } else {
                    //替換列表頭
                    hasToScrollValue += headView.getMeasuredWidth()-width;
                }
            }
            setMaxScrollValue(Integer.MAX_VALUE);
            requestLayout();
        }
    }
    public void removeHeadView(){
        if(hasHeadView&&headView!=null){
            hasHeadView=false;
            int left=headView.getLeft();
            int width=headView.getMeasuredWidth();
            removeViewInLayout(headView);

            if(headView.getRight()>=getShowStartEdge()) {//列表頭有顯示
                scrollValue = -(width+left-getShowStartEdge());
                hasToScrollValue=0;
            }else{
                scrollValue-=width;
                hasToScrollValue-=width;
            }
            requestLayout();
        }else{
            hasHeadView=false;
        }
    }

    public void setFootView(View view){
        if(view!=null) {
            if (hasFootView&&footView!=null) {
                removeViewInLayout(footView);
            }
            hasFootView=true;
            footView=view;
            addAndMeasureChild(footView, -1);
            requestLayout();
        }
    }
    private boolean isRemoveFootView=false;
    public void removeFootView(){
        if(hasFootView&&footView!=null){
            hasFootView=false;
            int left=footView.getLeft();
            mScroller.forceFinished(true);//用於修補由於快速華東的過程中移除尾視圖而出現bug
            removeViewInLayout(footView);

            if(left<getShowEndEdge()) {//表示需要回退
                hasToScrollValue -= getShowEndEdge()-left;
                isRemoveFootView=true;
            }
            requestLayout();
        }else{
            hasFootView=false;
        }
    }

    /**
     * 在onTouchEvent處理事件,讓子視圖優先消費事件
     * @param event
     * @return
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if(dealTouchEvent(event)) return true;
        return mGesture.onTouchEvent(event);
    }

    private GestureDetector.OnGestureListener mOnGesture = new GestureDetector.SimpleOnGestureListener() {

        @Override
        public boolean onDown(MotionEvent e) {
            mScroller.forceFinished(true);//點擊時停止滾動
            return true;
        }

        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
                               float velocityY) {
            minScrollValue=0;//在快速滑動時,不能出現類似下拉刷新的操作
            mScroller.fling(scrollValue, 0, (int)-velocityX, 0, 0, maxScrollValue, 0, 0);
            requestLayout();
            return true;
        }

        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2,
                                float distanceX, float distanceY) {
            if(isCanPullLeft()) {
                minScrollValue = Integer.MIN_VALUE;//手動滑動時可以出現類似下拉刷新的操作
            }else{
                minScrollValue = 0;
            }
            synchronized(HorizontalListView5.this){
                hasToScrollValue += (int)calDistanceVal(distanceX);
            }
            requestLayout();
            return true;
        }

        @Override
        public boolean onSingleTapConfirmed(MotionEvent e) {
            int startIndex=0;
            if(canShowScrollBar) startIndex=1;
            for(int i=0;i<getChildCount();i++){
                View child = getChildAt(i);
                if(child==mScrollBar) break;
                if (isEventWithinView(e, child)) {
                    if(hasHeadView&&i==0){
                        //點擊列表頭
                        if(OnHeadViewClickListener!=null)
                            OnHeadViewClickListener.onClick(child);
                    }else if(hasFootView&&i==getChildCount()-(1+startIndex)){
                        //點擊列表尾
                        if(OnFootViewClickListener!=null)
                            OnFootViewClickListener.onClick(child);
                    }else {
                        int position=firstItemIndex + i;
                        if(hasHeadView) position--;
                        if (getOnItemClickListener() != null) {
                            getOnItemClickListener().onItemClick(HorizontalListView5.this, child, position, adapter.getItemId(position));
                        }
                        if (getOnItemSelectedListener() != null) {
                            getOnItemSelectedListener().onItemSelected(HorizontalListView5.this, child, position, adapter.getItemId(position));
                        }
                    }
                    break;
                }

            }
            return true;
        }

        @Override
        public void onLongPress(MotionEvent e) {
            int startIndex=0;//這個值用於處理滾動條的影響
            if(canShowScrollBar) startIndex=1;
            int childCount = getChildCount();
            for (int i = 0; i < childCount; i++) {
                View child = getChildAt(i);
                if(child==mScrollBar) return;
                if (isEventWithinView(e, child)) {
                    if(hasHeadView&&i==0){
                        //長按列表頭
                        if(OnHeadViewLongClickListener!=null)
                            OnHeadViewLongClickListener.onLongClick(child);
                    }else if(hasFootView&&i==getChildCount()-(1+startIndex)){
                        //長按列表尾
                        if(OnFootViewLongClickListener!=null)
                            OnFootViewLongClickListener.onLongClick(child);
                    } else {
                        int position=firstItemIndex + i;
                        if(hasHeadView) position--;
                        if (getOnItemLongClickListener() != null) {
                            getOnItemLongClickListener().onItemLongClick(HorizontalListView5.this, child, position, adapter.getItemId(position));
                        }
                    }
                    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 isEventDownIntercept=false;
    private boolean isSpringBack=false;
    private boolean isCanPullDownInthis=true;
    /**
     * 在手勢解析器之前攔截事件
     * @param event
     * @return
     */
    private boolean dealTouchEvent(MotionEvent event){
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                isCanPullDownInthis=true;
                int startIndex=0;
                if(canShowScrollBar) startIndex=1;
                if(getChildCount()==startIndex){
                    isCanPullDownInthis=false;
                }else{
                    if(getChildAt(getChildCount()-(1+startIndex)).getRight()<getShowEndEdge()){
                        isCanPullDownInthis=false;
                    }
                }
                isEventDownIntercept=springBack();
                if (isEventDownIntercept) return true;
                break;
            case MotionEvent.ACTION_MOVE:
                return isEventDownIntercept;
            case MotionEvent.ACTION_UP:
                return springBack();
        }
        return false;
    }

    /**
     * 採用阻尼式算法,重新提取移動值
     * @param distanceVal
     * @return
     */
    private float calDistanceVal(float distanceVal){
        if(!isCanPullDownInthis&&distanceVal>0){
            return 0;
        }
        int startIndex=0;//這個值用於處理滾動條的影響
        if(canShowScrollBar) startIndex=1;
        if(getChildCount()>startIndex){
            int dis=getChildAt(0).getLeft()-getShowStartEdge();
            if(dis>0){//執行頭部拉伸阻尼式計算
                if(distanceVal>=0) distanceVal = distanceVal/2;
                else {
                    float sc = 1 - dis / (getShowWidth() * 0.4f);
                    sc = sc < 0 ? 0 : sc;
                    distanceVal = distanceVal * sc;
                }
                if(onPullLeftListener!=null)
                    onPullLeftListener.onScroll(distanceVal);
            }else {
                dis = getShowEndEdge() - getChildAt(getChildCount() - (1 + startIndex)).getRight();
                if (dis > 0 && getFirstItemLeftEdge() < getShowStartEdge()) {
                    //執行尾部拉伸阻尼式計算
                    if (distanceVal <= 0) distanceVal = distanceVal / 2;
                    else {
                        float sc = 1 - dis / (getShowWidth() * 0.4f);
                        sc = sc < 0 ? 0 : sc;
                        distanceVal = distanceVal * sc;
                    }
                    if (onPullRightListener != null)
                        onPullRightListener.onScroll(distanceVal);
                }
            }
        }
        return distanceVal;
    }

    /**
     * 執行回滾操作
     * @return
     */
    private boolean springBack(){
        isSpringBack=false;
        int startIndex=0;
        if(canShowScrollBar) startIndex=1;
        if(getChildCount()>startIndex){
            int dis=getChildAt(0).getLeft()-getShowStartEdge();
            if(dis>0){//執行頭部回滾操作
                mScroller.startScroll(scrollValue, 0, dis, 0, 500);
                isSpringBack=true;
                requestLayout();
                if(onPullLeftListener!=null
                        &&(dis>getShowWidth()/5||dis>120))
                    onPullLeftListener.onFullEvent();
                return true;
            }
            dis=getShowEndEdge()-getChildAt(getChildCount()-(1+startIndex)).getRight();
            if(dis>0&&getFirstItemLeftEdge()<getShowStartEdge()){
                //執行尾部回滾操作
                mScroller.startScroll(scrollValue, 0, -dis, 0, 500);
                isSpringBack=true;
                requestLayout();
                if(onPullRightListener!=null
                        &&(dis>getShowWidth()/5||dis>120))
                    onPullRightListener.onFullEvent();
                return true;
            }
        }

        return false;
    }

    private OnPullListener onPullLeftListener = null;
    private OnPullListener onPullRightListener = null;


    private OnClickListener OnHeadViewClickListener;
    private OnClickListener OnFootViewClickListener;
    private OnLongClickListener OnHeadViewLongClickListener;
    private OnLongClickListener OnFootViewLongClickListener;

    public void setOnPullLeftListener(OnPullListener onPullLeftListener) {
        this.onPullLeftListener = onPullLeftListener;
    }

    public void setOnPullRightListener(OnPullListener onPullRightListener) {
        this.onPullRightListener = onPullRightListener;
    }

    public void setOnHeadViewClickListener(OnClickListener onHeadViewClickListener) {
        OnHeadViewClickListener = onHeadViewClickListener;
    }

    public void setOnFootViewClickListener(OnClickListener onFootViewClickListener) {
        OnFootViewClickListener = onFootViewClickListener;
    }

    public void setOnHeadViewLongClickListener(OnLongClickListener onHeadViewLongClickListener) {
        OnHeadViewLongClickListener = onHeadViewLongClickListener;
    }

    public void setOnFootViewLongClickListener(OnLongClickListener onFootViewLongClickListener) {
        OnFootViewLongClickListener = onFootViewLongClickListener;
    }

    public interface OnPullListener{
        /**
         * 滾動時執行
         * @param dis 移動的位移值
         */
        void onScroll(float dis);

        /**
         * 事件確認(類似下拉刷新事件觸發)
         */
        void onFullEvent();
    }



    public synchronized void scrollTo(int x) {
        mScroller.startScroll(hasToScrollValue, 0, x - hasToScrollValue, 0);
        requestLayout();
    }

    public boolean isCanShowInMid() {
        return canShowInMid;
    }

    /**
     * 可以劇中顯示則不能使用左/右拉功能
     * @param canShowInMid
     */
    public void setCanShowInMid(boolean canShowInMid) {
        this.canShowInMid = canShowInMid;
        if(canShowInMid) {
            setCanPullLeft(false);
            setCanPullRight(false);
        }
    }

    public boolean isCanShowScrollBar() {
        return canShowScrollBar;
    }

    public void setCanShowScrollBar(boolean canShowScrollBar) {
        this.canShowScrollBar = canShowScrollBar;
    }

    public boolean isCanPullRight() {
        return canPullDown;
    }

    public void setCanPullRight(boolean canPullDown) {
        this.canPullDown = canPullDown;
        if (canPullDown) setCanShowInMid(false);
    }

    public boolean isCanPullLeft() {
        return canPullUp;
    }

    public void setCanPullLeft(boolean canPullUp) {
        this.canPullUp = canPullUp;
        if (canPullUp) setCanShowInMid(false);
    }

}

實現思路如下:

    1.在原來的版本上擴展滾動的邊界值

        在之前的版本代碼中,列表的滾動無法超出界面的可視區域,爲了實現左/右拉伸及回彈功能,必須擴展其滾動邊界值(在代碼中對應的屬性:maxScrollValue、minScrollValue);還必須考慮兼容以前的版本功能。

public void setMaxScrollValue(int maxScrollValue) {
    if(isCanPullRight()&&maxScrollValue!=Integer.MAX_VALUE) {
        if(isSpringBack){//在執行回彈操作
            this.maxScrollValue = Integer.MAX_VALUE;
        }else if(mScroller.isFinished()){//手動執行滾動操作
            this.maxScrollValue = Integer.MAX_VALUE;
        }else{
            if(maxScrollValue<0)this.maxScrollValue=0;
            else this.maxScrollValue = maxScrollValue;
        }
    }else{
        if(maxScrollValue<0)this.maxScrollValue=0;
        else this.maxScrollValue = maxScrollValue;
    }
}
private GestureDetector.OnGestureListener mOnGesture = new GestureDetector.SimpleOnGestureListener() {

    ......

    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
                           float velocityY) {
        minScrollValue=0;//在快速滑動時,不能出現類似下拉刷新的操作
        mScroller.fling(scrollValue, 0, (int)-velocityX, 0, 0, maxScrollValue, 0, 0);
        requestLayout();
        return true;
    }

    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2,
                            float distanceX, float distanceY) {
        if(isCanPullLeft()) {
            minScrollValue = Integer.MIN_VALUE;//手動滑動時可以出現類似下拉刷新的操作
        }else{
            minScrollValue = 0;
        }
        synchronized(HorizontalListView5.this){
            hasToScrollValue += (int)calDistanceVal(distanceX);
        }
        requestLayout();
        return true;
    }
    ......
}

 

    2.在OnGestureListener.onScroll()方法中採用阻尼算法重新計算滾動值

/**
 * 採用阻尼式算法,重新提取移動值
 * @param distanceVal
 * @return
 */
private float calDistanceVal(float distanceVal){
    if(!isCanPullDownInthis&&distanceVal>0){
        return 0;
    }
    int startIndex=0;//這個值用於處理滾動條的影響
    if(canShowScrollBar) startIndex=1;
    if(getChildCount()>startIndex){
        int dis=getChildAt(0).getLeft()-getShowStartEdge();
        if(dis>0){//執行頭部拉伸阻尼式計算
            if(distanceVal>=0) distanceVal = distanceVal/2;
            else {
                float sc = 1 - dis / (getShowWidth() * 0.4f);
                sc = sc < 0 ? 0 : sc;
                distanceVal = distanceVal * sc;
            }
            if(onPullLeftListener!=null)
                onPullLeftListener.onScroll(distanceVal);
        }else {
            dis = getShowEndEdge() - getChildAt(getChildCount() - (1 + startIndex)).getRight();
            if (dis > 0 && getFirstItemLeftEdge() < getShowStartEdge()) {
                //執行尾部拉伸阻尼式計算
                if (distanceVal <= 0) distanceVal = distanceVal / 2;
                else {
                    float sc = 1 - dis / (getShowWidth() * 0.4f);
                    sc = sc < 0 ? 0 : sc;
                    distanceVal = distanceVal * sc;
                }
                if (onPullRightListener != null)
                    onPullRightListener.onScroll(distanceVal);
            }
        }
    }
    return distanceVal;
}
private GestureDetector.OnGestureListener mOnGesture = new GestureDetector.SimpleOnGestureListener() {

    ......
    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2,
                            float distanceX, float distanceY) {
        if(isCanPullLeft()) {
            minScrollValue = Integer.MIN_VALUE;//手動滑動時可以出現類似下拉刷新的操作
        }else{
            minScrollValue = 0;
        }
        synchronized(HorizontalListView5.this){
            hasToScrollValue += (int)calDistanceVal(distanceX);
        }
        requestLayout();
        return true;
    }
    ......
}

    3.onTouchEvent()中採用事件攔截,實現回彈功能

/**
 * 在手勢解析器之前攔截事件
 * @param event
 * @return
 */
private boolean dealTouchEvent(MotionEvent event){
    switch (event.getAction()){
        case MotionEvent.ACTION_DOWN:
            isCanPullDownInthis=true;
            int startIndex=0;
            if(canShowScrollBar) startIndex=1;
            if(getChildCount()==startIndex){
                isCanPullDownInthis=false;
            }else{
                if(getChildAt(getChildCount()-(1+startIndex)).getRight()<getShowEndEdge()){
                    isCanPullDownInthis=false;
                }
            }
            isEventDownIntercept=springBack();
            if (isEventDownIntercept) return true;
            break;
        case MotionEvent.ACTION_MOVE:
            return isEventDownIntercept;
        case MotionEvent.ACTION_UP:
            return springBack();
    }
    return false;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
    if(dealTouchEvent(event)) return true;
    return mGesture.onTouchEvent(event);
}

    4.居中顯示功能和左/右拉伸功能不兼容,做好排異處理

/**
 * 可以劇中顯示則不能使用左/右拉功能
 * @param canShowInMid
 */
public void setCanShowInMid(boolean canShowInMid) {
    this.canShowInMid = canShowInMid;
    if(canShowInMid) {
        setCanPullLeft(false);
        setCanPullRight(false);
    }
}
public void setCanPullRight(boolean canPullDown) {
    this.canPullDown = canPullDown;
    if (canPullDown) setCanShowInMid(false);
}
public void setCanPullLeft(boolean canPullUp) {
    this.canPullUp = canPullUp;
    if (canPullUp) setCanShowInMid(false);
}

    5.爲左/右拉伸功能添加回調事件,同時設置啓動開關

private OnPullListener onPullLeftListener = null;
private OnPullListener onPullRightListener = null;

public void setOnPullLeftListener(OnPullListener onPullLeftListener) {
    this.onPullLeftListener = onPullLeftListener;
}

public void setOnPullRightListener(OnPullListener onPullRightListener) {
    this.onPullRightListener = onPullRightListener;
}

public interface OnPullListener{
    /**
     * 滾動時執行
     * @param dis 移動的位移值
     */
    void onScroll(float dis);

    /**
     * 事件確認(類似下拉刷新事件觸發)
     */
    void onFullEvent();
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章