横向ListView(四) —— 添加滚动条

在前面的文章已经介绍了横向ListView的基础实现及头尾视图的添加等的实现,这篇文章将介绍为横向ListView添加滚动条;这一功能的添加和前面章节有些不同,前面章节添加功能都是在原来的控件上追加的,而滚动条的实现是以一个独立的控件存在的,以组合的形式添加到横向ListView中。

 

滚动条的实现思路:

    1.计算横向ListView可见区域的宽度

    2.计算整个横向ListView中所有数据都显示时的视图宽度(即理论上整个列表应该有的宽度)

    3.计算出左边不可见的部分理论上应该有的宽度

    4.根据比例计算出当前滚动条的显示宽度及显示位置(即width和left的值)

    5.将滚动条控件组合到横向ListView中,同时设置显示开关

 

先上完整源码:

1.滚动条控件源码:

package com.hss.os.horizontallistview;

import android.content.Context;
import android.graphics.Color;
import android.os.Build;
import android.os.Handler;
import android.os.Message;
import android.support.annotation.RequiresApi;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;

/**
 * Created by Administrator on 2017/8/9.
 */

public class ScrollBar extends View {

    private AlphaAnimation animation;
    private int defaultVal=5;//滚动条的默认宽高值
    private ShowType type=ShowType.horizontal;

    private enum ShowType{
        horizontal,
        vertical
    }

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

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

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

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

    private void init(Context context){
        setBackgroundColor(Color.parseColor("#3c3f41"));//设置默认背景颜色
        //创建AlphaAnimation(透明度动画)
        animation=new AlphaAnimation(1.0f, 0.1f);
        //设置动画时间
        animation.setDuration(1500);
        //设置动画重复次数
        animation.setRepeatCount(0);
        animation.setAnimationListener(animationListener);
    }

    Handler handler=new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            startAnimation(animation);
            return false;
        }
    });


    private Animation.AnimationListener animationListener=new Animation.AnimationListener() {
        @Override
        public void onAnimationStart(Animation animation) {
        }

        @Override
        public void onAnimationEnd(Animation animation) {
            setVisibility(INVISIBLE);//使用INVISIBLE而不是使用GONE,是因为GONE会触发requestLayout()执行,导致界面刷新
        }

        @Override
        public void onAnimationRepeat(Animation animation) {
        }
    };

    /**
     * 将滚动条从父布局中移除
     * 必须调用这个方法执行移除操作,否则动画执行会有问题
     * @param parent
     */
    public void remove(ViewGroup parent){
        handler.removeMessages(0);
        //必须在从父布局中移除之前调用clearAnimation(),否则之后的动画执行会有问题
        clearAnimation();
        parent.removeViewInLayout(this);
    }

    /**
     * 控件没有使用头尾视图设定时使用
     * @param parent             父容器视图
     * @param firstItemIndex     在可见区域内第一个item的下标(不包含头视图)
     * @param lastItemIndex      在可见区域内最后一个item的下标(不包含尾视图)
     * @param itemCount          所有的item总个数(不包含头尾视图)
     */
    public void showVertical(ViewGroup parent,int firstItemIndex,int lastItemIndex,int itemCount){
        showVertical(parent,firstItemIndex,lastItemIndex,itemCount,0,0);
    }

    /**
     * 控件有使用头尾视图时使用
     * @param parent             父容器视图
     * @param firstItemIndex     在可见区域内第一个item的下标(不包含头视图)
     * @param lastItemIndex      在可见区域内最后一个item的下标(不包含尾视图)
     * @param itemCount          所有的item总个数(不包含头尾视图)
     * @param headVal            显示的头视图高度
     * @param footVal            显示的尾视图高度
     */
    public void showVertical(ViewGroup parent,int firstItemIndex,int lastItemIndex,int itemCount,int headVal,int footVal){
        type=ShowType.vertical;
        show(parent,firstItemIndex,lastItemIndex,itemCount,headVal,footVal);
    }

    /**
     * 控件没有使用头尾视图设定时使用
     * @param parent             父容器视图
     * @param firstItemIndex     在可见区域内第一个item的下标(不包含头视图)
     * @param lastItemIndex      在可见区域内最后一个item的下标(不包含尾视图)
     * @param itemCount          所有的item总个数(不包含头尾视图)
     */
    public void showHorizontal(ViewGroup parent,int firstItemIndex,int lastItemIndex,int itemCount){
        showHorizontal(parent,firstItemIndex,lastItemIndex,itemCount,0,0);
    }

    /**
     * 控件有使用头尾视图时使用
     * @param parent             父容器视图
     * @param firstItemIndex     在可见区域内第一个item的下标(不包含头视图)
     * @param lastItemIndex      在可见区域内最后一个item的下标(不包含尾视图)
     * @param itemCount          所有的item总个数(不包含头尾视图)
     * @param headVal            显示的头视图宽度
     * @param footVal            显示的尾视图宽度
     */
    public void showHorizontal(ViewGroup parent,int firstItemIndex,int lastItemIndex,int itemCount,int headVal,int footVal){
        type=ShowType.horizontal;
        show(parent,firstItemIndex,lastItemIndex,itemCount,headVal,footVal);
    }


    private int estimateVal=0;//预估的整个视图所有子项都显示出来时的高/宽(包含头尾视图)
    private int averageVal=0;//预估的每一个子视图的高/宽(item的平均高度)
    private int showCount=0;//当前显示的子项个数(不包含头、尾视图)

    /**
     *
     * @param parent             父容器视图
     * @param firstItemIndex     在可见区域内第一个item的下标(不包含头视图)
     * @param lastItemIndex      在可见区域内最后一个item的下标(不包含尾视图)
     * @param itemCount          所有的item总个数(不包含头尾视图)
     * @param headVal            显示的头视图高度
     * @param footVal            显示的尾视图高度
     */
    private void show(ViewGroup parent,int firstItemIndex,int lastItemIndex,int itemCount,int headVal,int footVal){
        setVisibility(INVISIBLE);//使用INVISIBLE而不是使用GONE,是因为GONE会触发requestLayout()执行,导致界面刷新
        if(parent.getChildCount()==0) return;//没有子视图则不显示滚动条
        //以下五行对数据的调整,是为了确保数据在逻辑上的正确性,确保之下的计算不会出现逻辑以外的情况
        firstItemIndex=firstItemIndex<0?0:firstItemIndex;
        lastItemIndex=lastItemIndex<0?0:lastItemIndex;
        lastItemIndex=lastItemIndex<firstItemIndex?firstItemIndex:lastItemIndex;
        itemCount=itemCount<=0?1:itemCount;
        itemCount=itemCount<=lastItemIndex? lastItemIndex+1:itemCount;

        if(lastItemIndex==0&&headVal==0&&footVal==0) return;//如果没有显示内容,则不显示滚动条

        showCount=lastItemIndex-firstItemIndex+1;
        ViewGroup.LayoutParams params=getLayoutParams();
        int left=0,top=0,right=0,bottom=0;
        switch (type){
            case vertical://显示竖向滚动条
                int childHeight=getAllChildHeight(parent);
                int visibleHeight=getParentVisibleHeight(parent);
                if (childHeight<visibleHeight) return;//如果显示的内容没有超过可见区域,则不显示滚动条
                if (childHeight==visibleHeight&&showCount==itemCount) return;//临界值

                //计算left、right值
                params.width = defaultVal;
                left=parent.getWidth()-parent.getPaddingRight()-params.width;
                right=left+params.width;

                // 计算top、bottom值
                // 计算top的时候需要做如下考虑:
                // 如果所有的子项都已经显示了,则需要采用精准显示方式
                // 如果不是所有的子项都已经显示了,则采用模糊估量的显示方式
                int topH=0;
                if(showCount==itemCount){//精准计算滚动条
                    estimateVal=childHeight;
                    topH=getChildOverHeightOfTop(parent);
                }else{
                    averageVal=(childHeight-headVal-footVal)/showCount;//预估每个item的高度(不包括头、尾视图)
                    estimateVal=averageVal*itemCount+headVal+footVal;//这里需要加上头、尾视图的值
                    topH = getChildOverHeightOfTop2(parent,headVal)+firstItemIndex*averageVal;
                }
                double hScale = visibleHeight/(estimateVal*1.0);
                double tScale = topH/(estimateVal*1.0);
                params.height = (int) (visibleHeight*hScale);
                top = (int) (parent.getPaddingTop()+visibleHeight*tScale);
                bottom=top+params.height;
                break;
            case horizontal://显示横向滚动条
            default:
                int childWidth=getAllChildWidth(parent);
                int visibleWidth=getParentVisibleWidth(parent);
                if (childWidth<visibleWidth) return;//如果显示的内容没有超过可见区域,则不显示滚动条
                if (childWidth==visibleWidth&&showCount==itemCount) return;//临界值

                // 计算top、bottom值
                params.height = defaultVal;
                top=parent.getHeight()-parent.getPaddingBottom()-params.height;
                bottom=top+params.height;

                //计算left、right值
                // 计算left的时候需要做如下考虑:
                // 如果所有的子项都已经显示了,则需要采用精准显示方式
                // 如果不是所有的子项都已经显示了,则采用模糊估量的显示方式
                int topW=0;
                if(showCount==itemCount){//精准计算滚动条
                    estimateVal=childWidth;
                    topW=getChildOverWidthOfLeft(parent);
                }else{
                    averageVal=(childWidth-headVal-footVal)/showCount;//预估每个item的宽度(不包括头、尾视图)
                    estimateVal=averageVal*itemCount+headVal+footVal;//这里需要加上头、尾视图的值
                    topW = getChildOverWidthOfLeft2(parent,headVal)+firstItemIndex*averageVal;
                }
                double wScale = visibleWidth/(estimateVal*1.0);
                double lScale = topW/(estimateVal*1.0);
                params.width = (int) (visibleWidth*wScale);
                left = (int) (parent.getPaddingLeft()+visibleWidth*lScale);
                right = left+params.width;
        }
        layout(left,top,right,bottom);
        setVisibility(VISIBLE);
        handler.sendEmptyMessageDelayed(0,1000);
    }






    /**
     * 获得所有孩子视图的总体高度
     * @param parent
     * @return
     */
    private int getAllChildHeight(ViewGroup parent){
        int val=0;
        for(int i=0;i<parent.getChildCount();i++){
            if(parent.getChildAt(i)!=this)
            val+=parent.getChildAt(i).getHeight();
        }
        return val;
    }
    /**
     * 获得父视图的可见区域高度
     * @param parent
     * @return
     */
    private int getParentVisibleHeight(ViewGroup parent){
        return parent.getHeight()-parent.getPaddingTop()-parent.getPaddingBottom();
    }
    /**
     * 获得视图顶端超出可见区域的孩子视图总高度
     * @param parent
     * @return
     */
    private int getChildOverHeightOfTop(ViewGroup parent){
        int val=0;
        int i=0;
        while(parent.getChildAt(i).getBottom()<parent.getPaddingTop()){
            val+=parent.getChildAt(i).getHeight();//如果整个item都在可见区域外,则叠加其高度
            i++;
        }
        if(parent.getChildAt(i).getTop()<parent.getPaddingTop()){
            //如果该item只有部分在可见区域外,则叠加其超出部分
            val+=parent.getPaddingTop()-parent.getChildAt(i).getTop();
        }
        return val;
    }
    private int getChildOverHeightOfTop2(ViewGroup parent,int headVal){
        int val=0;
        int i=0;
        if(headVal>0) i=1;//剔除列表头
        while(parent.getChildAt(i).getBottom()<parent.getPaddingTop()){
            i++;
        }
        if(parent.getChildAt(i).getTop()<parent.getPaddingTop()){
            //如果该item只有部分在可见区域外,则叠加其超出部分
            val+=parent.getPaddingTop()-parent.getChildAt(i).getTop();
        }
        if(headVal>0){//添加表头的值
            if(parent.getChildAt(0).getBottom()<parent.getPaddingTop()){
                val+=parent.getChildAt(i).getHeight();//如果整个item都在可见区域外,则叠加其高度
            } else if(parent.getChildAt(0).getTop()<parent.getPaddingTop()){
                //如果该item只有部分在可见区域外,则叠加其超出部分
                val+=parent.getPaddingTop()-parent.getChildAt(0).getTop();
            }
        }
        return val;
    }


    /**
     * 获得所有孩子视图的总体宽度
     * @param parent
     * @return
     */
    private int getAllChildWidth(ViewGroup parent){
        int val=0;
        for(int i=0;i<parent.getChildCount();i++){
            if(parent.getChildAt(i)!=this)
            val+=parent.getChildAt(i).getWidth();
        }
        return val;
    }
    /**
     * 获得父视图的可见区域宽度
     * @param parent
     * @return
     */
    private int getParentVisibleWidth(ViewGroup parent){
        return parent.getWidth()-parent.getPaddingLeft()-parent.getPaddingRight();
    }
    /**
     * 获得视图左边超出可见区域的孩子视图总宽度
     * @param parent
     * @return
     */
    private int getChildOverWidthOfLeft(ViewGroup parent){
        int val=0;
        int i=0;
        while(parent.getChildAt(i).getRight()<parent.getPaddingLeft()){
            val+=parent.getChildAt(i).getWidth();//如果整个item都在可见区域外,则叠加其宽度
            i++;
        }
        if(parent.getChildAt(i).getLeft()<parent.getPaddingLeft()){
            //如果该item只有部分在可见区域外,则叠加其超出部分
            val+=parent.getPaddingLeft()-parent.getChildAt(i).getLeft();
        }
        return val;
    }
    private int getChildOverWidthOfLeft2(ViewGroup parent,int headVal){
        int val=0;
        int i=0;
        if(headVal>0) i=1;//剔除列表头
        while(parent.getChildAt(i).getRight()<parent.getPaddingLeft()){
            i++;
        }
        if(parent.getChildAt(i).getLeft()<parent.getPaddingLeft()){
            //如果该item只有部分在可见区域外,则叠加其超出部分
            val+=parent.getPaddingLeft()-parent.getChildAt(i).getLeft();
        }
        if(headVal>0){//添加表头的值
            if(parent.getChildAt(0).getRight()<parent.getPaddingLeft()){
                val+=parent.getChildAt(0).getWidth();//如果整个item都在可见区域外,则叠加其宽度
            } else if(parent.getChildAt(0).getLeft()<parent.getPaddingLeft()){
                //如果该item只有部分在可见区域外,则叠加其超出部分
                val+=parent.getPaddingLeft()-parent.getChildAt(0).getLeft();
            }
        }
        return val;
    }
}

2.横向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.util.Log;
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 HorizontalListView4 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 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;//是否需要显示滚动条


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

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

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

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public HorizontalListView4(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;//列表发生滚动所能达到的最大位移值(这个由最后显示的列表项决定)
        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);

        Log.e("","============>>>>>left:"+left+" top:"+top+" right:"+right+" bottom:"+bottom);
        //需要先布局列表项再根据余下的空间布局列表头尾
        //布局列表项
        /*
        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()){
            post(new Runnable(){
                @Override
                public void run() {
                    requestLayout();
                }
            });
        }
    }

    /**
     * 计算这一次整体滚动偏移量
     * @return
     */
    private int calculateScrollValue(){
        int dx=0;

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

        if(hasToScrollValue<=0){
            hasToScrollValue=0;
            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()) {
                    maxScrollValue = scrollValue + getChildAt(getChildCount() - 1).getRight() - getShowEndEdge();
                }else{
                    maxScrollValue=0;
                }
            }
        }else{
            if(adapter!=null&&adapter.getCount()>0){

            }else {
                if (getChildCount() > 0
                        && getChildAt(getChildCount() - 1).getRight() >= getShowEndEdge()) {
                    maxScrollValue = scrollValue + getChildAt(getChildCount() - 1).getRight() - getShowEndEdge();
                } else {
                    maxScrollValue = 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{
                if(headView.getRight()>0)
                    right=headView.getRight()+dx;
                else
                    right=getShowStartEdge()+headView.getMeasuredWidth();
            }
            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{
                if(hasHeadView)
                    left=headView.getRight();
                else {
                    if(footView.getLeft()==0&&dx==0){//第一次赋值
                        left=getShowStartEdge();
                    }else{
                        left=footView.getLeft()+dx;
                    }
                }
            }
            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 0;
        }
    }
    /**
     * 获得列表视图中最后一个item View右边界值
     * @return
     */
    private int getLastItemRightEdge(){
        if(getListItemCount()>0) {
            return lastItemRightEdge;
        }else{
            if(hasFootView) return footView.getLeft();
            else return 0;
        }
    }
    /**
     * 取得视图可见区域的左边界
     * @return
     */
    private int getShowStartEdge(){
        return getPaddingLeft();
    }
    /**
     * 取得视图可见区域的右边界
     * @return
     */
    private int getShowEndEdge(){
        return getWidth()-getPaddingRight();
    }
    /**
     * 取得视图可见区域的宽度
     * @return
     */
    private int getShowWidth(){
        return getWidth()-getPaddingLeft()-getPaddingRight();
    }











    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;
                }
            }
            maxScrollValue=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();
        }
    }
    public void removeFootView(){
        if(hasFootView&&footView!=null){
            hasFootView=false;
            int left=footView.getLeft();
            removeViewInLayout(footView);

            if(left<getWidth()) {
                hasToScrollValue -= getWidth()-left-getShowStartEdge();
            }
            requestLayout();
        }else{
            hasFootView=false;
        }
    }

    /**
     * 在onTouchEvent处理事件,让子视图优先消费事件
     * @param event
     * @return
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        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) {
            mScroller.fling(hasToScrollValue, 0, (int)-velocityX, 0, 0, maxScrollValue, 0, 0);
            requestLayout();
            return true;
        }

        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2,
                                float distanceX, float distanceY) {

            synchronized(HorizontalListView4.this){
                hasToScrollValue += (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(hasHeadView&&i==0){
                        //点击列表头
                    }else if(hasFootView&&i==getChildCount()-1){
                        //点击列表尾
                    }else {
                        int position=firstItemIndex + i;
                        if(hasHeadView) position--;
                        if (getOnItemClickListener() != null) {
                            getOnItemClickListener().onItemClick(HorizontalListView4.this, child, position, adapter.getItemId(position));
                        }
                        if (getOnItemSelectedListener() != null) {
                            getOnItemSelectedListener().onItemSelected(HorizontalListView4.this, child, position, adapter.getItemId(position));
                        }
                    }
                    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(hasHeadView&&i==0){
                        //点击列表头
                    }else if(hasFootView&&i==getChildCount()-1){
                        //点击列表尾
                    }else {
                        int position=firstItemIndex + i;
                        if(hasHeadView) position--;
                        if (getOnItemLongClickListener() != null) {
                            getOnItemLongClickListener().onItemLongClick(HorizontalListView4.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());
        }
    };




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

    public boolean isCanShowInMid() {
        return canShowInMid;
    }

    public void setCanShowInMid(boolean canShowInMid) {
        this.canShowInMid = canShowInMid;
    }

    public boolean isCanShowScrollBar() {
        return canShowScrollBar;
    }

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

 

滚动条的实现思路:

    1.计算横向ListView可见区域的宽度

    2.计算整个横向ListView中所有数据都显示时的视图宽度(即理论上整个列表应该有的宽度)

        在计算这个值时,我分为两个方向考虑;一个是已经显示完所有的数据时的整个列表的宽度,这时是采用实际的显示值计算的;另一个正好相反,这时采用的是模糊算法,即通过已经显示的视图的值估量显示完所有的item时应该具备的值(理论值)。

    3.计算出左边不可见的部分理论上应该有的宽度

        这个值计算也是同步骤2,具体实现请看源码,源码中已经有相当的注解了

    4.根据比例计算出当前滚动条的显示宽度及显示位置(即width和left的值)

    5.将滚动条控件组合到横向ListView中,同时设置显示开关

        这一步值得注意一点:为了不影响横向ListView之前版本的源码逻辑,在每次布局时都需要先去除滚动条,等所有控件布局完毕再重新加入滚动条;还有一点,在去除滚动条之前先停止滚动条中的动画操作,如果没有停止,则后续的动画执行就会有问题,涉及代码如下:

/**
 * 将滚动条从父布局中移除
 * 必须调用这个方法执行移除操作,否则动画执行会有问题
 * @param parent
 */
public void remove(ViewGroup parent){
    handler.removeMessages(0);
    //必须在从父布局中移除之前调用clearAnimation(),否则之后的动画执行会有问题
    clearAnimation();
    parent.removeViewInLayout(this);
}

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章