横向ListView(四) —— 添加滚动条
2019独角兽企业重金招聘Python工程师标准>>>
在前面的文章已经介绍了横向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); }
转载于:https://my.oschina.net/u/3614895/blog/1505495
横向ListView(四) —— 添加滚动条相关推荐
- div横向超出可滚动,自动添加滚动条,自定义滚动条样式,
先看一下最终的效果图吧: 第一种文字内容超出显示滚动条: 父盒子:横向超出滚动:overflow-x: scroll; .box {width: 100%;box-sizing: border-box ...
- 横向ListView(一) ——开篇,基础逻辑实现
2019独角兽企业重金招聘Python工程师标准>>> 第一次写博文,写得不好的地方还望各位看客见谅 为了学习自定义软件开发,且定制出满足自己需求的控件(不需要将就地使用第三方源码) ...
- ECharts图标数据过多添加滚动条
ECharts图标数据过多添加滚动条 以echarts柱状图滚动条为示例,步骤如下: 1.存入数据 加入滚动条显示判断 var data = []; //这里存入数据 判断使用var scrollBa ...
- php表格自动添加滚动条,jsp中为表格添加水平滚动条的方法
首先,本项目中使用的是bootstrap框架,因此有些人会说,给表格设置自适应属性就可以了 这里要申明的是 bootsrtap自适应是针对当浏览器不占满整个屏幕,而是一半的时候才会出现横向的滚动条 而 ...
- jpanel网格布局添加滚动条_啥是前端开发工程师必会的5种网页布局方式?
作为前端开发工程师,布局方式有多种,针对不同的情况有不一样的处理,但是很多初学的同学都不知道这些情况,那么我们今天就来说说,那些前端开发工程师不可不知的5种布局方式! 一.静态布局(static la ...
- Android横向ListView功能实现
--在开发类似新闻功能的App过程中需要在新闻页面展示新闻附件,附件一多的话一行展示不完,换行又不好看,为此需要做像今日头条导航栏那样可以横向滑动的列表,不同点在于附件数量是动态添加的 可能有一个 也 ...
- extjs给panel添加滚动条_ExtJs Panel 滚动条设置
设置autoscroll:true同时出现横向和纵向滚动条. 不要设置autoscroll属性,或者autoscroll:false,然后设置bodyStyle : 'overflow-x:hidde ...
- div添加滚动条css属性代码
div如何添加滚动条,div中的内容太多如何添加横向滚动条或纵向滚动条?可以为div设置overflow属性来实现,码教程来详细说下div添加滚动条的方法: div添加滚动条的方法 div添加滚动条是 ...
- php滚动条代码,CSS_给DIV添加滚动条的实现代码,直接为div指定overflow属性为auto - phpStudy...
给DIV添加滚动条的实现代码 直接为div指定overflow属性为auto即可,但是必须指定div的高度,如下: 复制代码代码如下: 如果要出现水平滚动条,则: overflow-x:auto 同理 ...
最新文章
- 查看显卡显存_显卡显存越大性能就越好吗【详细介绍】
- 【推荐】让你事半功倍的交互体验自查清单
- mac下用vim编写程序
- dede image.class.php,DEDE模板下载织梦DEDE 核心类TypeLink.class.php功能剖析
- VsCode配置Python项目的setting.json和launch.json两个配置文件
- java 检测目录下的文件_如何在Java中检查文件是目录还是文件
- matlab中mod(10 3),matlab的rem()和mod()函数
- 常用数据类型使用转换详解
- W ndows10用于3D建模,Windows10系统自带3D builder应用有哪些作用
- submit汉化 亲测可用
- Java数据结构-约瑟夫问题(Joseph环)
- [刷机教程] android系列 adb操作命令详解,常用adb操作命令详解
- android9手机电池管家,腾讯电池管家APP
- 《数学之美》读书笔记
- 移动端百度点击软件操作方法及常见问题
- IO多路复用之epoll总结 http://www.cnblogs.com/Anker/archive/2013/08/17/3263780.html
- 数据库笔记 NO.1 ------------2020.03.26
- Intriguing Properties of Vision Transformers论文解析
- 远程控制桌面,外网电脑PC手机ios安卓mac远程桌面连接内网linux和windows主机,史上最全最详细图文教程
- java 判断域密码到期提醒_域帐号密码过期邮件提醒
热门文章
- mysql+enable+sql+log_CentOS7下利用rsyslog+loganalyzer配置日志服务器及Linux和windows客户端配置...
- java数据类型划分_一张图搞定java数据类型的划分
- python实现一个简单的加法计算器_Python tkinter实现简单加法计算器代码实例
- mysql有选择地输出数据_有条件地选择MYSQL列
- ios 设备获取idfa_超4成用户选择升级iOS 14,35%苹果设备已无法获取IDFA
- 【视频课】永久免费!5小时快速掌握Pytorch框架入门及实战
- 全球及中国抗菌溶液行业深度调研与前景研究建议报告2022年
- 统计每日单量MySQL语句
- python输出格式化及函数format
- vc通过COM方式调用CertEnroll