在公司做錢眼這個app時,其中有一個界面比較複雜,上邊需要顯示很多的資料,下邊還有一個清單。如下圖。
(公司産品尚未上線,是以用了一個同類型app的界面了)。當我看到這個美工切圖以後,我自然反應使用一個listview實作所有邏輯,事實上我已經實作了。在【熱帖、新貼、新聞、公告、球友】上邊相當于head,之下的都市listview的item。雖然實作了,但是感覺代碼比較亂。也在網上找了一些資料,發現可以通過scrollview嵌套listview的方式實作,【熱帖、新貼、新聞、公告、球友】之上是一部分,下邊是listview,整體使用scrollview包裹。這楊的話,在adapter中的代碼要少得多,不像我現在adapter裡邊的代碼還是比較複雜的。是以,我就想研究一下scrollview裡邊嵌套listview有沒有什麼問題,如果沒有問題的話,接下來在重構代碼時,把這一塊的代碼就改了。
使用listview的三種情況:
1. 整個界面就一個listview,通過上下滑動就能顯示所有的資料。
2. 整個界面分兩個部分,上部分顯示其他的資料,下半部分顯示listview。我們通過滑動listview就能顯示所有的資料了。但是我們想滑動listview時,能不能上半部分也能滑動呢?
3. 整個界面分兩個部分,上半個部分顯示listview,下邊顯示其他。那麼當listview顯示很多資料的時候,下邊就不能正常顯示出來?
以上第二、三我們都可以通過scollview嵌套listview來實作。
那我們就先來看看再scrollview裡邊嵌套listview時,不做任何處理會出現什麼情況呢。
先看看我的Activity中的代碼:
package com.example.listviewdemo;
import java.util.ArrayList;
import java.util.List;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
public class MainActivity extends Activity {
private ListView mListview;
private ArrayList<String> datas;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mListview = (ListView) findViewById(R.id.lv);
initData();
TestAdapter adapter = new TestAdapter(this, R.layout.listview_item, datas);
mListview.setAdapter(adapter);
}
private void initData(){
datas = new ArrayList<String>();
for(int i = ; i < ; i++){
datas.add("test " + i);
}
}
class TestAdapter extends ArrayAdapter<String>{
public TestAdapter(Context context, int resource, List<String> objects) {
super(context, resource, objects);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
String text = datas.get(position);
ViewHolder holder = null;
if (convertView == null){
holder = new ViewHolder();
convertView = View.inflate(MainActivity.this, R.layout.listview_item, null);
holder.tv = (TextView) convertView.findViewById(R.id.tv);
convertView.setTag(holder);
}else{
holder = (ViewHolder) convertView.getTag();
}
holder.tv.setText(text);
return convertView;
}
class ViewHolder{
TextView tv;
}
}
}
代碼很簡單吧。再看看我的xml布局。
<?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="match_parent"
android:orientation="vertical" >
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="listview上邊的文字1"
android:textSize="40sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="listview上邊的文字2"
android:textSize="40sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="listview上邊的文字3"
android:textSize="40sp" />
<ListView
android:id="@+id/lv"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
</ListView>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="listview下邊的文字1"
android:textSize="40sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="listview下邊的文字2"
android:textSize="40sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="listview下邊的文字3"
android:textSize="40sp" />
</LinearLayout>
</ScrollView>
</LinearLayout>
有一個細節需要注意,在scrollview中隻能放入一個子容器控件。這個xml布局表示,該界面上邊放三個TextView,下邊也放TextView。結果顯示慘不忍睹,listview中隻能顯示一兩個item。這也是我放棄使用scrollview包裹listview原因吧。那麼有沒有解決的方法吧。
其實有兩種方法:
1. 對于listview添加adapter以後,把所有的item的高度和分割線的高度計算出來,計算一個總和。最後給listview設定LayoutParams即可。那我們就來拭目以待吧。
在Activity中調用setListViewHeightBasedOnChildren。該方法代碼如下:
/**
* 用于解決ScrollView嵌套listview時,出現listview隻能顯示一行的問題
* @param listView
*/
public void setListViewHeightBasedOnChildren(ListView listView) {
// 擷取ListView對應的Adapter
ListAdapter listAdapter = listView.getAdapter();
if (listAdapter == null) {
return;
}
int totalHeight = ;
for (int i = , len = listAdapter.getCount(); i < len; i++) {
// listAdapter.getCount()傳回資料項的數目
View listItem = listAdapter.getView(i, null, listView);
// 計算子項View 的寬高
listItem.measure(, );
// 統計所有子項的總高度
totalHeight += listItem.getMeasuredHeight();
}
ViewGroup.LayoutParams params = listView.getLayoutParams();
params.height = totalHeight+ (listView.getDividerHeight() * (listAdapter.getCount() - ));
// listView.getDividerHeight()擷取子項間分隔符占用的高度
// params.height最後得到整個ListView完整顯示需要的高度
listView.setLayoutParams(params);
}
結果你也發現,listview所有的條目都能顯示出來了。滾動效果也有了。這樣做以後存在兩個問題。
1.1 每次都顯示listview的第一個item。比如我的例子中,顯示時總是顯示第一個【test 0】,上邊的TextView都自動滾動最上邊了。但是,這個問題我倒是有辦法解決的。可以在oncreate方法中,setListViewHeightBasedOnChildren(mListview);方法之後調用。
mScrollView.post(new Runnable() {
@Override
public void run() {
mScrollView.scrollTo(, );
}
});
但第一個問題還是解決不了。所有使用這種方法也是有弊端的。
- 我們看看第二種方法吧。自定義listview重寫onMeasure方法。具體實作如下:
package com.example.listviewdemo;
import android.widget.ListView;
public class ScrollViewWithListView extends ListView {
public ScrollViewWithListView(android.content.Context context,
android.util.AttributeSet attrs) {
super(context, attrs);
}
/**
* Integer.MAX_VALUE >> 2,如果不設定,系統預設設定是顯示兩條
*/
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> ,
MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
}
}
使用這種方法也能實作scrollview嵌套listview的情況。當同樣出現第一個方法出現的問題。
綜上所述,在scrollview中嵌套listview方案是可行的。目前有很多的APP的list界面都采用這種設計。上邊有一部分,下邊是一個listview。整體是可以滾動的,當往上滾動到一段距離以後,就會出現固定一個懸浮框。
講了這麼多的理論,我們就來做一個執行個體吧。
效果圖:
當下邊的商品往上滾動時,【可樂雞翅】這個titlebar就會固定在頂部了。請看下邊代碼。
package com.jimstin.topfloatdemo.view;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ImageView;
import android.widget.ScrollView;
public class MyScrollView extends ScrollView {
View mTopView;
View mFlowView;
public MyScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
if(mTopView != null && mFlowView != null) {
if(t >= mTopView.getHeight()) {
mFlowView.setVisibility(View.VISIBLE);
} else {
mFlowView.setVisibility(View.GONE);
}
}
}
/**
* 監聽浮動view的滾動狀态
* @param topView 頂部區域view,即當ScrollView滑動的高度要大于等于哪個view的時候隐藏floatview
* @param flowView 浮動view,即要哪個view停留在頂部
*/
public void listenerFlowViewScrollState(View topView, View flowView) {
mTopView = topView;
mFlowView = flowView;
}
}
重寫onScrollChanged方法,當滾動的高度大于等于上邊view的高度時,就把隐藏的懸浮框顯示出來。針對公司的産品,接下來優化方向也是基于這個方向進行改造。
代碼下載下傳
代碼下載下傳