公司的這個(gè)項(xiàng)目做了一年寄疏,感覺(jué)自己有了很大的提升甘苍。決定把這一年來(lái)做的比較好比較有用的一些東西抽出來(lái)記錄下來(lái)痴荐。既能整理自己的知識(shí)樹(shù),又能給其他朋友一些參考恰画。這篇講的是如何做一個(gè)可固定列頭列表滑動(dòng)的listview宾茂。
剛開(kāi)始做這個(gè)的時(shí)候,在網(wǎng)上查閱了大量資料拴还,也下載了很多其他人提供的demo跨晴,參考了他們的思路。但是總是要不就是不符合我的需求片林,要不就是有些bug端盆。最后,自己嘗試著編寫费封,經(jīng)過(guò)不斷的更改和修復(fù)焕妙,終于完成了這個(gè)功能。
首先也是比較重要的一點(diǎn)是孝偎,listview的item布局访敌,代碼如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:descendantFocusability="blocksDescendants"
android:orientation="horizontal">
<TextView
android:id="@+id/tv_line"
android:layout_width="80dp"
android:layout_height="50dp"
android:gravity="center"
android:text="表頭"
android:textColor="@android:color/black" />
<View
android:layout_width="0.1dp"
android:layout_height="50dp"
android:background="@android:color/black" />
<!--攔截子控件的響應(yīng)事件-->
<com.example.lanyee.demofixheadlist.InterceptRelayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:focusable="false">
<com.example.lanyee.demofixheadlist.ChartHScrollView
android:id="@+id/scroll_item"
android:layout_width="wrap_content"
android:layout_height="50dp"
android:overScrollMode="never"
android:scrollbars="none">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:focusable="false"
android:gravity="center"
android:orientation="horizontal">
<TextView
android:id="@+id/tv_1"
android:layout_width="40dp"
android:layout_height="match_parent"
android:gravity="center"
android:text="列1" />
<View
android:layout_width="0.1dp"
android:layout_height="match_parent"
android:background="@android:color/black" />
<TextView
android:id="@+id/tv_2"
android:layout_width="60dp"
android:layout_height="match_parent"
android:gravity="center"
android:singleLine="true"
android:text="列2" />
<View
android:layout_width="0.1dp"
android:layout_height="match_parent"
android:background="@android:color/black" />
<TextView
android:id="@+id/tv_3"
android:layout_width="80dp"
android:layout_height="match_parent"
android:gravity="center"
android:singleLine="true"
android:text="列3" />
<View
android:layout_width="0.1dp"
android:layout_height="match_parent"
android:background="@android:color/black" />
<TextView
android:id="@+id/tv_4"
android:layout_width="60dp"
android:layout_height="match_parent"
android:gravity="center"
android:singleLine="true"
android:text="列4" />
<View
android:layout_width="0.1dp"
android:layout_height="match_parent"
android:background="@android:color/black" />
<TextView
android:id="@+id/tv_5"
android:layout_width="60dp"
android:layout_height="match_parent"
android:gravity="center"
android:singleLine="true"
android:text="列5" />
<View
android:layout_width="0.1dp"
android:layout_height="match_parent"
android:background="@android:color/black" />
<TextView
android:id="@+id/tv_6"
android:layout_width="80dp"
android:layout_height="match_parent"
android:gravity="center"
android:singleLine="true"
android:text="列6" />
<View
android:layout_width="0.1dp"
android:layout_height="match_parent"
android:background="@android:color/black" />
<TextView
android:id="@+id/tv_7"
android:layout_width="80dp"
android:layout_height="match_parent"
android:gravity="center"
android:singleLine="true"
android:text="列7" />
<View
android:layout_width="0.1dp"
android:layout_height="match_parent"
android:background="@android:color/black" />
<TextView
android:id="@+id/tv_8"
android:layout_width="80dp"
android:layout_height="match_parent"
android:gravity="center"
android:singleLine="true"
android:text="列8" />
</LinearLayout>
</com.example.lanyee.demofixheadlist.ChartHScrollView>
</com.example.lanyee.demofixheadlist.InterceptRelayout>
</LinearLayout>
其中,InterceptRelayout是起攔截子控件的響應(yīng)事件作用的衣盾。listview的item中的ChartHScrollView子控件是不響應(yīng)觸摸事件的寺旺,觸摸事件統(tǒng)一交給列頭的ChartHScrollView來(lái)處理,然后遍歷通知item中的ChartHScrollView進(jìn)行滑動(dòng)势决。ChartHScrollView是自定義的view阻塑,繼承自HorizonScrollView,用觀察者模式果复。注意 android:descendantFocusability="blocksDescendants"
是覆蓋子類控件而直接獲得焦點(diǎn)陈莽,如果需要有item點(diǎn)擊響應(yīng),必須加這句代碼。這兩個(gè)類的代碼如下:
public class InterceptRelayout extends RelativeLayout{
public InterceptRelayout(Context context) {
super(context);
}
public InterceptRelayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public InterceptRelayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return true;
}
}
public class ChartHScrollView extends HorizontalScrollView {
//滑動(dòng)事件的觀察者們走搁,即listview的item中的ChartHScrollView
private ChartScrollViewObservable observable;
//滑動(dòng)距離監(jiān)聽(tīng)
private ScrollViewMoveDistanceListener scrollViewMoveDistanceListener;
public ChartHScrollView(Context context) {
super(context);
observable = new ChartScrollViewObservable();
}
public ChartHScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
observable = new ChartScrollViewObservable();
}
public ChartHScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
observable = new ChartScrollViewObservable();
}
public void addObserver(ChartHScrollView observer) {
observable.addObserver(observer);
}
public void removeObserver(ChartHScrollView observer) {
observable.addObserver(observer);
}
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
//通知觀察者們独柑,當(dāng)前滑動(dòng)了多遠(yuǎn)
observable.notifyObservers(l, t);
super.onScrollChanged(l, t, oldl, oldt);
if (scrollViewMoveDistanceListener != null)
scrollViewMoveDistanceListener.scrollviewMoveDistance(l);
}
public void setScrollViewMoveDistanceListener(ScrollViewMoveDistanceListener scrollViewMoveDistanceListener) {
this.scrollViewMoveDistanceListener = scrollViewMoveDistanceListener;
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
//當(dāng)scrollview的布局發(fā)生改變時(shí),使其與列頭view滑動(dòng)的距離保持一致
if (scrollViewMoveDistanceListener != null)
scrollTo(scrollViewMoveDistanceListener.getHeadScrollViewMoveDistance(), 0);
}
}
接下來(lái)就是Adapter了私植,Adapter要做的事情很簡(jiǎn)單忌栅。在getview的回調(diào)中,當(dāng)contentView為null的時(shí)候曲稼,用列頭的ChartHScrollView 對(duì)象索绪,調(diào)用addObserver()方法,傳入item中的ChartHScrollView 對(duì)象參數(shù)贫悄。注意瑞驱!只需要在當(dāng)contentView為null的時(shí)候,添加觀察者就行了窄坦,因?yàn)楫?dāng)contentView唤反!=null時(shí),是復(fù)用的之前的item嫡丙,所以觀察者對(duì)象集已經(jīng)有此對(duì)象了拴袭。代碼如下:
public class Adapter extends BaseAdapter implements ScrollViewMoveDistanceListener, AdapterView.OnItemClickListener {
//列頭的scrollview
private ChartHScrollView hScrollView;
//當(dāng)前滑動(dòng)的距離,當(dāng)item中的ChartHScrollView發(fā)生布局改變時(shí),需要此參數(shù)使其滑動(dòng)scrollDistance距離曙博,與列頭保持一致拥刻。
private volatile int scrollDistance = 0;
private ArrayList<Integer> datas;
public Adapter(ChartHScrollView hScrollView, ArrayList<Integer> datas) {
this.hScrollView = hScrollView;
this.datas = datas;
hScrollView.setScrollViewMoveDistanceListener(this);
}
@Override
public int getCount() {
return datas.size();
}
@Override
public Object getItem(int position) {
return datas.get(position);
}
@Override
public long getItemId(int position) {
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder;
if (convertView == null) {
convertView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_layout, null);
viewHolder = new ViewHolder(convertView);
//將觀察者對(duì)象添加進(jìn)對(duì)象集
hScrollView.addObserver(viewHolder.itemScroll);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
viewHolder.tvLine.setText("行" + datas.get(position));
viewHolder.tv1.setText(String.valueOf(datas.get(position)));
viewHolder.tv2.setText(String.valueOf(datas.get(position) + 1));
viewHolder.tv3.setText(String.valueOf(datas.get(position) + 2));
viewHolder.tv4.setText(String.valueOf(datas.get(position) + 3));
viewHolder.tv5.setText(String.valueOf(datas.get(position) + 4));
viewHolder.tv6.setText(String.valueOf(datas.get(position) + 5));
viewHolder.tv7.setText(String.valueOf(datas.get(position) + 6));
viewHolder.tv8.setText(String.valueOf(datas.get(position) + 7));
return convertView;
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Toast.makeText(parent.getContext(), "點(diǎn)擊位置" + position, Toast.LENGTH_SHORT).show();
}
class ViewHolder {
private TextView tvLine;
private ChartHScrollView itemScroll;
private TextView tv1;
private TextView tv2;
private TextView tv3;
private TextView tv4;
private TextView tv5;
private TextView tv6;
private TextView tv7;
private TextView tv8;
public ViewHolder(View view) {
tvLine = (TextView) view.findViewById(R.id.tv_line);
tv1 = (TextView) view.findViewById(R.id.tv_1);
tv2 = (TextView) view.findViewById(R.id.tv_2);
tv3 = (TextView) view.findViewById(R.id.tv_3);
tv4 = (TextView) view.findViewById(R.id.tv_4);
tv5 = (TextView) view.findViewById(R.id.tv_5);
tv6 = (TextView) view.findViewById(R.id.tv_6);
tv7 = (TextView) view.findViewById(R.id.tv_7);
tv8 = (TextView) view.findViewById(R.id.tv_8);
itemScroll = (ChartHScrollView) view.findViewById(R.id.scroll_item);
itemScroll.setScrollViewMoveDistanceListener(Adapter.this);
}
}
/**
* 列頭的ChartHScrollView移動(dòng)的距離
* @param distance
*/
@Override
public void scrollviewMoveDistance(int distance) {
scrollDistance = distance;
}
/**
* 當(dāng)item中的ChartHScrollView發(fā)生布局改變時(shí),滑動(dòng)scrollDistance使其保持與列頭一致
* @return 列頭的ChartHScrollView移動(dòng)的距離
*/
@Override
public int getHeadScrollViewMoveDistance() {
return scrollDistance;
}
}
接下來(lái)這個(gè)很重要父泳,就是listview上的touch和列頭上的touch事件處理般哼。代碼如下:
public class ListViewAndHeadViewTouchHandle implements View.OnTouchListener {
//列頭的scrollView
private ChartHScrollView scrollView;
private ListView listView;
//列頭
private LinearLayout headLine;
public ListViewAndHeadViewTouchHandle(LinearLayout headLine, ListView listView) {
scrollView = (ChartHScrollView) headLine.findViewById(R.id.scroll_item);
this.headLine = headLine;
this.listView = listView;
listView.setOnTouchListener(this);
headLine.setOnTouchListener(this);
}
float x1 = 0, y1 = 0, x2 = 0, y2 = 0;
//區(qū)分當(dāng)前的滑動(dòng)狀態(tài)
boolean isClick = false;
boolean isHorizonMove = true;
boolean isVerticalMove = false;
@Override
public boolean onTouch(View arg0, MotionEvent arg1) {
switch (arg1.getAction()) {
case MotionEvent.ACTION_DOWN:
x1 = arg1.getX();
y1 = arg1.getY();
//當(dāng)在列頭 和 listView控件上touch時(shí),將這個(gè)touch的事件分發(fā)給 ScrollView和listView處理惠窄。
//一個(gè)view只有在接收到了down事件蒸眠,才能繼續(xù)接收之后的觸摸事件。對(duì)這一塊不太熟悉的建議先去看看touch事件的分發(fā)機(jī)制杆融。
scrollView.onTouchEvent(arg1);
listView.onTouchEvent(arg1);
isClick = false;
isHorizonMove = false;
isVerticalMove = false;
break;
case MotionEvent.ACTION_MOVE:
x2 = arg1.getX();
y2 = arg1.getY();
if (Math.abs(x2 - x1) < 10 && Math.abs(y2 - y1) < 10) {
//判定當(dāng)前動(dòng)作是點(diǎn)擊
isClick = true;
isHorizonMove = false;
isVerticalMove = false;
} else {
isClick = false;
if (Math.abs(x2 - x1) > Math.abs(y2 - y1)) {
//水平
//如果之前有過(guò)垂直操作,則不再更改方向
if (!isVerticalMove) {
isHorizonMove = true;
isVerticalMove = false;
}
} else {
//垂直
//如果之前有過(guò)水平操作,則不再更改方向
if (!isHorizonMove) {
isVerticalMove = true;
isHorizonMove = false;
}
}
}
//垂直動(dòng)作或點(diǎn)擊動(dòng)作交給listView來(lái)處理
if (isVerticalMove || isClick) {
listView.onTouchEvent(arg1);
} else {
//水平動(dòng)作交給列頭的scrollView來(lái)處理,列頭的scrollView接收后楞卡,會(huì)回調(diào)onScrollChanged(),重寫onScrollChanged()通知觀察者們滑動(dòng)
scrollView.onTouchEvent(arg1);
}
break;
case MotionEvent.ACTION_UP:
if (Math.abs(arg1.getX() - x1) < 10 && Math.abs(arg1.getY() - y1) < 10) {
isClick = true;
}
//isClick && arg0 != headLine這個(gè)判斷是防止在列頭點(diǎn)擊時(shí),listview會(huì)響應(yīng)點(diǎn)擊事件
if ((isClick && arg0 != headLine) || isVerticalMove) {
listView.onTouchEvent(arg1);
} else {
scrollView.onTouchEvent(arg1);
}
isClick = false;
isHorizonMove = false;
isVerticalMove = false;
break;
}
return true;
}
}
接下來(lái)就是mainActivity的代碼內(nèi)容和布局內(nèi)容了脾歇。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.example.lanyee.demofixheadlist.MainActivity">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/darker_gray">
<include
android:id="@+id/headLine"
layout="@layout/item_layout"/>
</RelativeLayout>
<ListView
android:id="@+id/listview"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
public class MainActivity extends AppCompatActivity {
private ListView listView;
private LinearLayout headLine;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listView = (ListView) findViewById(R.id.listview);
headLine = (LinearLayout) findViewById(R.id.headLine);
ArrayList<Integer> datas = new ArrayList<>();
for (int i = 0; i < 50; i++) {
datas.add(i);
}
Adapter adapter = new Adapter((ChartHScrollView) headLine.findViewById(R.id.scroll_item), datas);
listView.setAdapter(adapter);
//統(tǒng)一處理列頭和listview的touch事件
new ListViewAndHeadViewTouchHandle(headLine, listView);
listView.setOnItemClickListener(adapter);
}
}
監(jiān)聽(tīng)文件代碼:
public interface ScrollViewMoveDistanceListener {
void scrollviewMoveDistance(int distance);
int getHeadScrollViewMoveDistance();
}
所有的代碼都已經(jīng)貼出來(lái)啦蒋腮,代碼中也加了比較詳細(xì)的注釋。平時(shí)很少編輯文章藕各,所以表述可能不是很清楚池摧。另外這個(gè)文本編輯器貼代碼塊好像不是很好用。如果有疑問(wèn)或更好的建議激况,歡迎留言評(píng)論作彤,我們共同探討膘魄。
最后謝謝你的觀看。
轉(zhuǎn)載請(qǐng)注明出處竭讳。