滾動條的實現思路:
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);
}