天天看點

android開發中使用scrollView嵌套Listview

在公司做錢眼這個app時,其中有一個界面比較複雜,上邊需要顯示很多的資料,下邊還有一個清單。如下圖。

android開發中使用scrollView嵌套Listview

(公司産品尚未上線,是以用了一個同類型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(, );

            }
        });
           

但第一個問題還是解決不了。所有使用這種方法也是有弊端的。

  1. 我們看看第二種方法吧。自定義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。整體是可以滾動的,當往上滾動到一段距離以後,就會出現固定一個懸浮框。

講了這麼多的理論,我們就來做一個執行個體吧。

效果圖:

android開發中使用scrollView嵌套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的高度時,就把隐藏的懸浮框顯示出來。針對公司的産品,接下來優化方向也是基于這個方向進行改造。

代碼下載下傳

代碼下載下傳