安卓自定义布局显示流式搜索记录

老规矩,先上效果图(环境:API 30 , AS 4.0

OKK,开始动手!

第一步:自定义流式布局 XFlowLayout ,继承ViewGroup,然后重写 onMeasure()onLayout()方法

import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;import java.util.ArrayList;
import java.util.List;/*** <p>*      自定义的流式布局,用来显示搜索历史记录* </p>*/
public class XFlowLayout extends ViewGroup {private static final String TAG = "XFlowLayout";/*** 子项位置列表*/private List<Rect> mChildrenPositionList = new ArrayList<>();/*** 显示的最大行数,默认无限*/private int mMaxLines = Integer.MAX_VALUE;/*** 子项可见数*/private int mVisibleItemCount;public XFlowLayout(Context context) {super(context);}public XFlowLayout(Context context, AttributeSet attrs) {super(context, attrs);}public XFlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {// 清除之前的位置mChildrenPositionList.clear();// 测量所有子元素measureChildren(widthMeasureSpec, heightMeasureSpec);int widthSize = MeasureSpec.getSize(widthMeasureSpec);int widthMode = MeasureSpec.getMode(widthMeasureSpec);int heightSize = MeasureSpec.getSize(heightMeasureSpec);int heightMode = MeasureSpec.getMode(heightMeasureSpec);int[] a = helper(widthSize);int measuredHeight = 0;// EXACTLY 模式:对应指定大小和 match_parentif (heightMode == MeasureSpec.EXACTLY) {measuredHeight = heightSize;}// AT_MOST 模式,对应 wrap_contentelse if (heightMode == MeasureSpec.AT_MOST) {measuredHeight = a[0];}int measuredWidth = 0;if (widthMode == MeasureSpec.EXACTLY) {measuredWidth = widthSize;} else if (widthMode == MeasureSpec.AT_MOST) {measuredWidth = a[1];}setMeasuredDimension(measuredWidth, measuredHeight);}/*** 在 wrap_content 情况下,得到布局的测量高度和测量宽度* 返回值是一个有两个元素的数组 a,a[0] 代表测量高度,a[1] 代表测量宽度*/private int[] helper(int widthSize) {boolean isOneRow = true;    // 是否是单行int width = getPaddingLeft();   // 记录当前行已有的宽度int height = getPaddingTop();   // 记录当前行已有的高度int maxHeight = 0;      // 记录当前行的最大高度int currLine = 1;       // 记录当前行数for (int i = 0; i < getChildCount(); i++) {View child = getChildAt(i);// 获取当前子元素的 marginLayoutParams params = child.getLayoutParams();MarginLayoutParams mlp;if (params instanceof MarginLayoutParams) {mlp = (MarginLayoutParams) params;} else {mlp = new MarginLayoutParams(params);}// 记录子元素所占宽度和高度int childWidth = mlp.leftMargin + child.getMeasuredWidth() + mlp.rightMargin;int childHeight = mlp.topMargin + child.getMeasuredHeight() + mlp.bottomMargin;maxHeight = Math.max(maxHeight, childHeight);// 判断是否要换行if (width + childWidth + getPaddingRight() > widthSize) {// 加上该行的最大高度height += maxHeight;// 重置 width 和 maxHeightwidth = getPaddingLeft();maxHeight = childHeight;isOneRow = false;currLine++;if (currLine > mMaxLines) {break;}}// 存储该子元素的位置,在 onLayout 时设置Rect rect = new Rect(width + mlp.leftMargin,height + mlp.topMargin,width + childWidth - mlp.rightMargin,height + childHeight - mlp.bottomMargin);mChildrenPositionList.add(rect);// 加上该子元素的宽度width += childWidth;}int[] res = new int[2];res[0] = height + maxHeight + getPaddingBottom();res[1] = isOneRow ? width + getPaddingRight() : widthSize;return res;}@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {// 布置子 View 的位置int n = Math.min(getChildCount(), mChildrenPositionList.size());for (int i = 0; i < n; i++) {View child = getChildAt(i);Rect rect = mChildrenPositionList.get(i);child.layout(rect.left, rect.top, rect.right, rect.bottom);}mVisibleItemCount = n;}/*** 设置 Adapter*/public void setAdapter(Adapter adapter) {// 移除之前的视图removeAllViews();// 添加 itemint n = adapter.getItemCount();for (int i = 0; i < n; i++) {ViewHolder holder = adapter.onCreateViewHolder(this);adapter.onBindViewHolder(holder, i);View child = holder.itemView;addView(child);}}/*** 设置最多显示的行数*/public void setMaxLines(int maxLines) {mMaxLines = maxLines;}/*** 获取显示的 item 数*/public int getVisibleItemCount() {return mVisibleItemCount;}public abstract static class Adapter<VH extends ViewHolder> {public abstract VH onCreateViewHolder(ViewGroup parent);public abstract void onBindViewHolder(VH holder, int position);public abstract int getItemCount();}/*** 视图搭载器*/public abstract static class ViewHolder {public final View itemView;public ViewHolder(View itemView) {if (itemView == null) {throw new IllegalArgumentException("itemView为空");}this.itemView = itemView;}}
}

参考RecyclerView的设计,我们也在XFlowLayout中定义了一个视图搭载器ViewHolder和一个适配器Adapter,然后用起来就和RecyclerView一样啦~~~~~

第二步:仿照RecyclerView的使用步骤,得写一个子项的布局文件

clf_history_item.xml

效果图:

<?xml version="1.0" encoding="utf-8"?><TextView android:id="@+id/tv_clf_record_name"android:layout_width="wrap_content"android:layout_height="wrap_content"android:background="@drawable/selector_his"android:text="asdasdasd"android:textSize="15sp"android:textColor="#ffffff"android:padding="11dp"xmlns:android="http://schemas.android.com/apk/res/android" />

第三步:写适配器,模仿RecyclerView的适配器写法;需要注意的是要在适配器中设置子项之间的距离

/*** <p>*     流式布局的适配器*     需要在这里给子项设置margin,xml设置不生效* </p>*/
public class XFlowAdapter extends XFlowLayout.Adapter<XFlowAdapter.FlowViewHolder> {private static final String TAG = "XFlowAdapter";private List<HistoryRecord> mHistoryList = new ArrayList<>();private Context mContext;private OnSearchFromHistory mListener;public XFlowAdapter(List<HistoryRecord> mHistoryList,  Context mContext) {this.mHistoryList = mHistoryList;this.mContext = mContext;}@Overridepublic FlowViewHolder onCreateViewHolder(ViewGroup parent) {View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.clf_history_item, parent,false);// 设置marginsetItemMargin(view);return new FlowViewHolder(view);}@Overridepublic void onBindViewHolder(FlowViewHolder holder, int position) {holder.setData(position);}@Overridepublic int getItemCount() {return mHistoryList.size();}/*** ViewHolder*/class FlowViewHolder extends XFlowLayout.ViewHolder{private TextView tv_record_name;// 字段值,这个值是从历史记录表中取出的数据private String historyRecord;private int position;public FlowViewHolder(View itemView) {super(itemView);tv_record_name = itemView.findViewById(R.id.tv_clf_record_name);tv_record_name.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {// 这里根据具体的点击子项执行什么逻辑// 我这里是根据字段去执行搜索逻辑mListener.onHistorySearch(historyRecord);}});}public void setData(int position) {this.position = position;historyRecord = mHistoryList.get(position).getRecordName();tv_record_name.setText(historyRecord);}}/*** 给子项设置margin** @param view*/public void setItemMargin(View view){ViewGroup.MarginLayoutParams marginLayoutParams = new ViewGroup.MarginLayoutParams(view.getLayoutParams());// 设置子项的marginint margin = dip2px(mContext, 5);marginLayoutParams.setMargins(margin, margin ,margin, margin);view.setLayoutParams(marginLayoutParams);}/*** 子项点击事件接口*/public interface OnSearchFromHistory{/*** 搜索* * @param historyRecord*/void onHistorySearch(String historyRecord);}/*** 设置子项点击事件** @param listener*/public void setOnSearchFromHistory(OnSearchFromHistory listener){this.mListener = listener;}/*** 根据手机的分辨率从 dip 的单位 转成为 px(像素)* * @param context* @param dpValue* @return*/public  int dip2px(Context context, float dpValue) {final float scale = context.getResources().getDisplayMetrics().density;return (int) (dpValue * scale + 0.5f);}
}

OKK,到这里就可以开始使用了。GO!

第四步:布局使用,这里是我的demo里面的搜索页面SearchFragment,只要关注这个流式布局在哪展示就行了

效果图:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:fitsSystemWindows="true"android:layout_width="match_parent"android:layout_height="match_parent"android:background="#222227"android:clickable="true"tools:context=".view.fragment.SearchFragment"><RelativeLayoutandroid:id="@+id/ll_search"android:layout_width="match_parent"android:layout_height="60dp"android:layout_alignParentTop="true"android:background="#222227"android:elevation="10dp"android:paddingTop="11dp"><ImageViewandroid:id="@+id/img_back"android:layout_width="20dp"android:layout_height="20dp"android:layout_alignParentLeft="true"android:layout_centerVertical="true"android:layout_marginLeft="20dp"android:background="@drawable/new_back_3" /><EditTextandroid:id="@+id/edt_search"android:layout_width="270dp"android:layout_height="35dp"android:layout_centerHorizontal="true"android:background="@drawable/shape_edit_search"android:hint=" Find Your Music ~"android:paddingLeft="7dp"android:singleLine="true"android:textColor="#ffffff"android:textColorHint="#6c6c6c"android:textSize="15sp" /><TextViewandroid:id="@+id/bt_click_to_search"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignParentRight="true"android:layout_centerVertical="true"android:layout_marginRight="9dp"android:text="搜索"android:textColor="#CCFF99"android:textSize="16sp" /></RelativeLayout><TextViewandroid:id="@+id/tv_type"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_below="@+id/ll_search"android:layout_marginLeft="22dp"android:layout_marginTop="8dp"android:layout_marginBottom="8dp"android:text="单曲"android:textColor="#ffffff"android:textSize="15sp"android:textStyle="bold"android:visibility="invisible" /><com.scwang.smart.refresh.layout.SmartRefreshLayoutandroid:id="@+id/srl_refresh"android:layout_width="match_parent"android:layout_height="match_parent"android:layout_below="@+id/tv_type"android:background="#222227"app:srlEnablePreviewInEditMode="true"><com.scwang.smart.refresh.header.ClassicsHeaderandroid:layout_width="match_parent"android:layout_height="wrap_content" /><androidx.recyclerview.widget.RecyclerViewandroid:id="@+id/rcv_search_result"android:layout_width="match_parent"android:layout_height="match_parent"android:layout_marginTop="20dp"android:overScrollMode="never" /><com.scwang.smart.refresh.footer.ClassicsFooterandroid:layout_width="match_parent"android:layout_height="wrap_content" /></com.scwang.smart.refresh.layout.SmartRefreshLayout><includelayout="@layout/layout_loading"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true" /><RelativeLayoutandroid:id="@+id/rl_history"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_below="@id/ll_search"android:layout_marginTop="10dp"android:visibility="visible"><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="历史搜索记录"android:textColor="#f1f3f9"android:textSize="15sp"android:layout_marginLeft="10dp"/><ImageViewandroid:id="@+id/img_delete_all"android:layout_height="20dp"android:layout_width="20dp"android:background="@drawable/select_delete"android:layout_alignParentRight="true"android:layout_marginRight="10dp"/><com.example.yan_music.widge.XFlowLayoutandroid:layout_below="@id/img_delete_all"android:id="@+id/cfl_history"android:layout_width="match_parent"android:layout_height="wrap_content"android:background="#222227"android:layout_marginTop="10dp"android:layout_marginLeft="5dp"android:layout_marginRight="5dp"/></RelativeLayout>
</RelativeLayout>

第五步:代码使用,在代码里面初始化布局,适配器,设置数据源,展示布局

 ======  SearchFragment 中的部分代码 =====/*** 搜索记录的流式布局+适配器+数据源*/
private XFlowLayout cfl_history;
private XFlowAdapter mCflAdapter;
// HistoryRecord.java是个记录Bean类(自定义就行了)
private List<HistoryRecord> mHistoryList = new ArrayList<>();... /*** 显示搜索历史记录*/public void showSearchHistory() {// 将<历史记录数据库>中的所有记录查询出来并添加到历史记录列表mHistoryListList<HistoryRecord> queryList = LitePal.findAll(HistoryRecord.class);mHistoryList.addAll(queryList);rl_history.setVisibility(View.VISIBLE);mCflAdapter = new XFlowAdapter(mHistoryList, iSearchPresenterImp, mMainActivity);mCflAdapter.setOnSearchFromHistory(this);cfl_history.setAdapter(mCflAdapter);cfl_history.setMaxLines(20);}

到这里,实现完毕。

安卓自定义布局显示流式搜索记录相关推荐

  1. Kotlin 第一弹:自定义 ViewGroup 实现流式标签控件

    古人学问无遗力, 少壮工夫老始成.纸上得来终觉浅, 绝知此事要躬行. – 陆游 <冬夜读书示子聿> 上周 Google I/O 大会的召开,宣布了 Kotlin 语言正式成为了官方开发语言 ...

  2. 静态布局、自适应布局、流式布局、响应式布局、弹性布局简析、BFC

    静态布局:给页面元素设置固定的宽度和高度,单位用px,当窗口缩小,会出现滚动条,拉动滚动条显示被遮挡内容.针对不同分辨率的手机端,分别写不同的样式文件.例如:浏览器窗口是1000px,那么最小的宽度是 ...

  3. 46、Flutter之 布局组件 流式布局Wrap,Flow

    Row 和 Colum 如果子 widget 超出屏幕范围,则会报溢出错误,如: Row(children: <Widget>[Text("xxx"*100)], ); ...

  4. CSS之布局系列--静态布局、流式布局、自适应布局、响应式布局的概念及区别

    原文网址:CSS之布局系列--静态布局.流式布局.自适应布局.响应式布局的概念及区别静态布局.流式布局.自适应布局.响应式布局的概念及区别_IT利刃出鞘的博客-CSDN博客 简介 说明 本文介绍前端的 ...

  5. 安卓自定义电量显示图标

    安卓自定义电量显示图标-----使用广播监听电量的变化,获取实时电量并使用自定义view画出对应的图标,看图: 自定义view实现实时电量显示:BatteryView.java import andr ...

  6. 静态布局、流式布局、自适应布局、弹性布局、响应式布局

    静态布局.流式布局.自适应布局.弹性布局.响应式布局 前端的布局主要有: 静态布局.流式布局.自适应布局.弹性布局.响应式布局等. 一.静态布局: 尺寸上一律使用px;同时限制外层容器的大小:中间的宽 ...

  7. Android自定义组合布局,Android 流式布局 + 自定义组合控件

    自定义组合控件 package yanjupeng.bawei.com.day09.two; import android.content.Context; import android.util.A ...

  8. android 自定义flowlayout,Android 流式布局FlowLayout 实现关键字标签

    FlowLayout Android 流式布局FlowLayout 实现关键字标签 效果图 使用方法 在项目根目录的build.gradle文件中加入如下代码 maven { url "ht ...

  9. 【移动端网页布局】流式布局案例 ④ ( Banner 栏制作 | 固定定位 | 标准流 | 百分比宽度设置 )

    文章目录 一.Banner 栏样式及核心要点 1.实现效果 2.核心要点分析 二.完整代码示例 1.HTML 标签结构 2.CSS 样式 3.展示效果 一.Banner 栏样式及核心要点 1.实现效果 ...

最新文章

  1. [***.launch] is neither a launch file in package [***] nor is [***] a launch
  2. JScrollPane 双滚动条
  3. 从偏远的小山村出来的孩子,一路的 “辛酸史”
  4. python教程:几个基础类型循环删除
  5. SQL中显示查询结果的前几条记录
  6. Sharepoint Ribbon Loaction
  7. 分布式代码管理系统Git实践
  8. js关于字面量与构造函数创建对象的几点理解
  9. 《C++ Primer》第五版课后习题解答_第六章(1)(01-07)
  10. c++输出的值精确到小数点后5位_直击灵魂——圆周率小数点后3位到12411亿位到底有啥用?...
  11. 复盘:什么是权重衰减?深度学习权重衰减
  12. 如何把电脑加上公司的域
  13. 此刻,投资自己,才是最好的投资
  14. Puppet学习之hiera(8)
  15. python最强脚本工具_python脚本工具最百里自瞄
  16. 推荐六款图片素材网站
  17. 如何提取视频中的音频?简单方法来啦
  18. ADAMS旋转运动副的添加以及注意事项
  19. 使用阿里云的oss对图片加水印并且字体大小自适应(阿里云oss暂不支持字体大小自适应)
  20. 破解一 3 内购破解之顶端加代码

热门文章

  1. 1313. 老曹骑士 TJ
  2. Log4J的入门简介学习【转】
  3. 龙蜥社区开源 coolbpf,BPF 程序开发效率提升百倍
  4. oracle存储多少条数据类型,Oracle目前可以存储极大的对象,这是因为它引入了四种新的数据类型。其中哪一种大对象数据类型在数...
  5. 分支与循环语句(下)
  6. 移动通信:1G到5G发展过程简析 -- 什么是5G?
  7. 技术专栏 | 为什么要基于模型设计?
  8. 飞腾S2500平台PCIe SWITCH下热插拔验证
  9. (转)人在德国:芦笋季节话芦笋
  10. everything搜索指定路径下的多个文件