在前面的文章已經介紹了橫向ListView的基礎實現及頭尾視圖的添加等的實現,這篇文章將介紹爲橫向ListView添加滾動條;這一功能的添加和前面章節有些不一樣,前面章節添加功能都是在原來的控件上追加的,而滾動條的實現是以一個獨立的控件存在的,以組合的形式添加到橫向ListView中。java
滾動條的實現思路:android
1.計算橫向ListView可見區域的寬度算法
2.計算整個橫向ListView中全部數據都顯示時的視圖寬度(即理論上整個列表應該有的寬度)api
3.計算出左邊不可見的部分理論上應該有的寬度緩存
4.根據比例計算出當前滾動條的顯示寬度及顯示位置(即width和left的值)ide
5.將滾動條控件組合到橫向ListView中,同時設置顯示開關佈局
先上完整源碼:post
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源碼:ui
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); }