1. 程式人生 > >利用熱門標籤佈局,實現單選列表

利用熱門標籤佈局,實現單選列表

昨天一個克鄭問我一個佈局實現效果如下:


當時有點思路也沒細想,今天整理一下分享給大家:

其實這個佈局的效果實現起來並不困難,方法也有很多,我大致講一下我的思路。

整體列表我選擇用listview,難點是listview的item怎麼去實現。假如資料固定的,我只需要寫兩個不用的item,然後判斷型別讓listview去展示就行了。當然這是不可能的,這樣拓展性太差了,假如資料是動態的,我就需要多次更改item的佈局。我們繼續分析,我們可以把item分為兩部分,一個是上面的標題,一個是下面的選項。這樣一來,我們就把重點放到這個選項的實現就可以了。我們可以發現這個選項其實就和我們經常用到的熱門標籤佈局是一樣的,這樣一想我們是不是給這個熱門標籤佈局加一個單選的功能就可以了呢。嗯下面我們就試一下:

熱門標籤佈局網上有很多,我找了一個修改如下:

package com.android.demo.zhangs.customlayout;

/**
 * Created by Admin on 2016/4/29.
 */
import java.util.ArrayList;
import java.util.List;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;

public class 
FlowLayout extends ViewGroup { //給上層暴露介面,去實現點選操作 public interface OnItemClickListener { public void OnItemClick(int position); } public FlowLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } public FlowLayout(Context context, AttributeSet attrs) { this
(context, attrs, 0); } public FlowLayout(Context context) { this(context, null); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //父控制元件傳進來的寬度和高度以及對應的測量模式 int sizeWidth = MeasureSpec.getSize(widthMeasureSpec); int modeWidth = MeasureSpec.getMode(widthMeasureSpec); int sizeHeight = MeasureSpec.getSize(heightMeasureSpec); int modeHeight = MeasureSpec.getMode(heightMeasureSpec); // wrap_content int width = 0; int height = 0; // 記錄每一行的寬度和高度 int lineWidth = 0; int lineHeight = 0; // 獲取子view的個數 int cCount = getChildCount(); for (int i = 0; i < cCount; i++) { View child = getChildAt(i); // 測量子View的寬和高 measureChild(child, widthMeasureSpec, heightMeasureSpec); // 得到LayoutParams MarginLayoutParams lp = (MarginLayoutParams) child .getLayoutParams(); // View佔據的寬度 int childWidth = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin; // View佔據的高度 int childHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin; // 換行時候 if (lineWidth + childWidth > sizeWidth - getPaddingLeft() - getPaddingRight()) { // 對比得到最大的寬度 width = Math.max(width, lineWidth); // 重置lineWidth lineWidth = childWidth; // 記錄行高 height += lineHeight; lineHeight = childHeight; } else // 不換行 { // 疊加行寬 lineWidth += childWidth; // 得到最大行高 lineHeight = Math.max(lineHeight, childHeight); } // 處理最後一個子View的情況 if (i == cCount - 1) { width = Math.max(lineWidth, width); height += lineHeight; } } Log.e("TAG", "sizeWidth = " + sizeWidth); Log.e("TAG", "sizeHeight = " + sizeHeight); setMeasuredDimension( // modeWidth == MeasureSpec.EXACTLY ? sizeWidth : width + getPaddingLeft() + getPaddingRight(), modeHeight == MeasureSpec.EXACTLY ? sizeHeight : height + getPaddingTop() + getPaddingBottom()// ); } /** * 儲存所有子View */ private List<List<View>> mAllViews = new ArrayList<List<View>>(); /** * 每一行的高度 */ private List<Integer> mLineHeight = new ArrayList<Integer>(); @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { mAllViews.clear(); mLineHeight.clear(); // 獲取當前ViewGroup的寬度 int width = getWidth(); int lineWidth = 0; int lineHeight = 0; //記錄當前行的view List<View> lineViews = new ArrayList<View>(); int cCount = getChildCount(); for (int i = 0; i < cCount; i++) { View child = getChildAt(i); MarginLayoutParams lp = (MarginLayoutParams) child .getLayoutParams(); int childWidth = child.getMeasuredWidth(); int childHeight = child.getMeasuredHeight(); //如果需要換行 if (childWidth + lineWidth + lp.leftMargin + lp.rightMargin > width - getPaddingLeft() - getPaddingRight()) { //記錄LineHeight mLineHeight.add(lineHeight); //記錄當前行的Views mAllViews.add(lineViews); //重置行的寬高 lineWidth = 0; lineHeight = childHeight + lp.topMargin + lp.bottomMargin; //重置view的集合 lineViews = new ArrayList<View>(); } lineWidth += childWidth + lp.leftMargin + lp.rightMargin; lineHeight = Math.max(lineHeight, childHeight + lp.topMargin + lp.bottomMargin); lineViews.add(child); }// for end // 處理最後一行 mLineHeight.add(lineHeight); mAllViews.add(lineViews); // 設定子View的位置 int left = getPaddingLeft(); int top = getPaddingTop(); // 獲取行數 int lineNum = mAllViews.size(); for (int i = 0; i < lineNum; i++) { // 當前行的views和高度 lineViews = mAllViews.get(i); lineHeight = mLineHeight.get(i); for (int j = 0; j < lineViews.size(); j++) { View child = lineViews.get(j); // 判斷是否顯示 if (child.getVisibility() == View.GONE) { continue; } MarginLayoutParams lp = (MarginLayoutParams) child .getLayoutParams(); int lc = left + lp.leftMargin; int tc = top + lp.topMargin; int rc = lc + child.getMeasuredWidth(); int bc = tc + child.getMeasuredHeight(); // 進行子View進行佈局 child.layout(lc, tc, rc, bc); left += child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin; } left = getPaddingLeft(); top += lineHeight; } } /** * 與當前ViewGroup對應的LayoutParams */ @Override public LayoutParams generateLayoutParams(AttributeSet attrs) { return new MarginLayoutParams(getContext(), attrs); } public void setOnItemClickClick(final OnItemClickListener onItemClickListener) { for (int i = 0; i < getChildCount(); i++) { final int finalI = i; getChildAt(i).setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (onItemClickListener != null) { for (int j = 0; j < getChildCount(); j++) { getChildAt(j).setBackgroundResource(R.drawable.shape_normal); } getChildAt(finalI).setBackgroundResource(R.drawable.shape_click); onItemClickListener.OnItemClick(finalI); } } }); } } } 有了這個自定義的佈局(可以直接放到自己的專案中),listview的item佈局item_listview.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">

    <TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="35dp"
android:gravity="center_vertical"
android:layout_marginLeft="10dp"
/>
    <!--到時候換成自己的包名即可-->
<com.android.demo.zhangs.customlayout.FlowLayout
android:id="@+id/option_group"
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
    </com.android.demo.zhangs.customlayout.FlowLayout>

</LinearLayout>
這樣一來我們只需要給這個FlowLayout動態新增TextView就可以了,為了方便給TextView新增樣式,我們再定義一個item_flow.xml作為選項的佈局檔案:
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/tv_item"
android:layout_width="70dp"
android:layout_height="40dp"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:background="@drawable/shape_normal"
android:gravity="center"
android:text="全部"
>

</TextView>

因為選項在選中未選中背景是不一樣的所以在這我定義了兩個shape,如下:
shape_normal.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#ffffff"/>
    <corners android:radius="5dp"/>
    <stroke android:color="#dcdcdc" android:width="1dp"/>
</shape>
shape_click.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#00ffff"/>
    <corners android:radius="5dp"/>
    <stroke android:color="#dcdcdc" android:width="1dp"/>
</shape>
佈局檔案都準備好了,下面我們就看一下,在adapter裡面怎麼實現就可以了:
package com.android.demo.zhangs.customlayout;

import android.content.Context;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.BaseAdapter;
import android.widget.TextView;
import android.widget.Toast;

import java.util.List;

/**
 * Created by Admin on 2016/4/29.
 */
public class MyAdapter extends BaseAdapter {
    private List<String> titles;
    private List<List<String>> options;
    private Context mContext;
    public MyAdapter(Context context, List<String> titles, List<List<String>> options) {
        this.mContext = context;
        this.titles = titles;
        this.options = options;
    }

    @Override
public int getCount() {
        return titles.size();
    }

    @Override
public Object getItem(int position) {
        return titles.get(position);
    }

    @Override
public long getItemId(int position) {
        return position;
    }

    @Override
public View getView(final int position, View convertView, ViewGroup parent) {
        ViewHolder holder = null;
        if (convertView == null) {
            convertView = LayoutInflater.from(mContext).inflate(R.layout.item_listview, null);
            holder = new ViewHolder();
            holder.title = (TextView) convertView.findViewById(R.id.title);
            holder.options = (FlowLayout) convertView.findViewById(R.id.option_group);
            convertView.setTag(holder);

        } else {
            holder = (ViewHolder) convertView.getTag();
        }
        holder.title.setText(titles.get(position));
        for (int i = 0; i < options.get(position).size(); i++) {
            TextView option = (TextView) View.inflate(mContext, R.layout.item_flow, null);
            //動態設定引數,將TextView的寬動態設定為螢幕寬度減去margin的三分之一
            ViewGroup.MarginLayoutParams lp = new ViewGroup.MarginLayoutParams((getScreenWidth(mContext)-dp2px(mContext,20)*2-dp2px(mContext,10)*2)/3
, dp2px(mContext,40));
            lp.leftMargin = dp2px(mContext,10);
            lp.rightMargin = dp2px(mContext,10);
            lp.topMargin = dp2px(mContext,10);
            lp.bottomMargin = dp2px(mContext,10);
            option.setText(options.get(position).get(i));
            holder.options.addView(option, lp);
        }
            //這是我自己定義的點選事件
            holder.options.setOnItemClickClick(new FlowLayout.OnItemClickListener() {
            @Override
public void OnItemClick(int pos) {
                 //在這寫自己需要進行的操作
                 Toast.makeText(mContext, options.get(position).get(pos), Toast.LENGTH_SHORT).show();
            }
        });
        return convertView;
    }

    class ViewHolder {
        TextView title;
        FlowLayout options;
    }

    /**
     * dppx
     *
     * @param context
* @return
*/
public static int dp2px(Context context, float dpVal) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                dpVal, context.getResources().getDisplayMetrics());
    }
    /**
     * 獲得螢幕寬度
*
     * @param context
* @return
*/
public static int getScreenWidth(Context context) {
        WindowManager wm = (WindowManager) context
                .getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics outMetrics = new DisplayMetrics();
        wm.getDefaultDisplay().getMetrics(outMetrics);
        return outMetrics.widthPixels;
    }

    /**
     * 獲得螢幕高度
*
     * @param context
* @return
*/
public static int getScreenHeight(Context context) {
        WindowManager wm = (WindowManager) context
                .getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics outMetrics = new DisplayMetrics();
        wm.getDefaultDisplay().getMetrics(outMetrics);
        return outMetrics.heightPixels;
    }

}
資料是我模擬的,可以根據自己的實際需求進行更改,最後好像就剩下在activity進行呼叫了,activity的佈局就一個listview我就不貼了:
下面是activity的程式碼:
package com.android.demo.zhangs.customlayout;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.ListView;
import android.widget.TextView;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {
    private ListView mListView;
    private List<String> titles;
    private List<List<String>> options;
    private MyAdapter mAdapter;


    @Override
protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mListView=(ListView)findViewById(R.id.listview);
        initData();
        mAdapter=new MyAdapter(this,titles,options);
        mListView.setAdapter(mAdapter);

    }
    private void initData(){
        titles=new ArrayList<>();
        options=new ArrayList<>();
        titles.add("按距離範圍");
        titles.add("需求類別");
        titles.add("其他");
        List<String> options1=new ArrayList<>();
        options1.add("全部");
        options1.add("10公里");
        options1.add("20公里");
        options1.add("30公里");
        options1.add("40公里");
        options1.add("50公里");
        List<String> options2=new ArrayList<>();
        options2.add("全部");
        options2.add("吊裝");
        options2.add("技改");
        options2.add("定檢");
        options2.add("清洗");
        List<String> options3=new ArrayList<>();
        options3.add("全部");
        options3.add("其他");
        options.add(options1);
        options.add(options2);
        options.add(options3);
    }
}
好了,我們看一下執行效果:


效果大致實現了,剩下一些小細節稍微更改一下就可以了~