昨天一个克郑问我一个布局实现效果如下:
当时有点思路也没细想,今天整理一下分享给大家:
其实这个布局的效果实现起来并不困难,方法也有很多,我大致讲一下我的思路。
整体列表我选择用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; } /** * dp转px * * @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); } }好了,我们看一下运行效果:
效果大致实现了,剩下一些小细节稍微更改一下就可以了~