一个自定义选项listview,超过实际宽度会自动换行。
emmmmmm,查了会百度,才知道这个自定义view叫标签流式布局,不过这个是按自己思路写的。
后续实现了一个viewgroup的[AS3.0.1]自定义ViewGroup的学习,写一个FlowLayout布局
可以先看下效果!
效果展示
使用如下
xml布局
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.gjn.viewdemo.MainActivity">
<com.gjn.viewdemo.OptionListView
android:id="@+id/olv"
android:layout_margin="20dp"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</FrameLayout>
activity
public class MainActivity extends AppCompatActivity {
private OptionListView olv;
private List<String> list;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
olv = findViewById(R.id.olv);
list = new ArrayList<>();
list.add("我是btn1111");
list.add("btn2222");
list.add("asdasd");
list.add("我是什么?");
list.add("测试宽度");
list.add("??????????");
olv.setOptionStrList(list);
}
}
开始设计自定义view
设计思路
其实很简单就是计算每一个加入的view的宽度,然后计算下一个view加入之后是否会超过自定义view的实际宽度,会的话就跳过换行
计算代码如下
private void calculateRow() {
List<Integer> viewWidths = new ArrayList<>();
//超过限制宽度强制设为最大
for (View view : optionViewList) {
int width = view.getWidth();
if (width == 0) {
view.measure(0, 0);
width = view.getMeasuredWidth();
}
if (width > Width){
width = Width;
}
viewWidths.add(width);
}
//计算需要生成的行数 每行需要放置多少个view
rowList = new ArrayList<>();
//记录加到第几个view
int size = 0;
for (int i = 0; i < viewWidths.size(); i++) {
rowList.add(new ArrayList<View>());
int futureWidth = 0;
for (int j = size; j < optionViewList.size(); j++) {
//计算加上下次宽度是否超过屏幕宽度
//超过跳出for
futureWidth += viewWidths.get(j);
if (futureWidth <= Width){
rowList.get(i).add(optionViewList.get(j));
//设置点击事件
setOnclick(j, optionViewList.get(j));
}else {
break;
}
}
//记录加到第几个view
size += rowList.get(i).size();
//判断是否全部view加载完毕
if (size >= optionViewList.size()){
break;
}
}
}
以上代码我做了如下操作
- 计算是否单一一个view的宽度直接越界
如果单独一个view就超过了自定义view的宽度,就把这个view设置成实际宽度
- 计算行列数
首先设置一个记录加载到第几个view的参数
再次是新建一行然后计算当前行数的宽度加上下一个view的宽度是否超过了自定义view宽度,如果没有就继续增加view,否则跳过这次循环,新建下一行。
最后当记录的参数等于总共view的数量就跳出全部循环。
- 其他说明
这边的rowList
是一个List<List<View>>
对象,直接记录了每行有几个view
具体实现
除了上面的计算稍微提及下,其他都是很简单的设置代码了,这边就不细讲了,直接把全部自定义view的代码贴出来了。具体也有一些注释说明
package com.gjn.viewdemo;
import android.content.Context;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
/**
* Created by gjn on 2018/5/11.
*/
public class OptionListView extends LinearLayout {
private static final String TAG = "OptionListView";
private Context context;
private List<String> optionStrList;
private List<View> optionViewList;
private List<TextView> textViewList;
private List<List<View>> rowList;
private int Width;
private int select = 0;
private boolean isSelect = true;
private viewOnClickListener listener;
public OptionListView(Context context) {
this(context, null);
}
public OptionListView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public OptionListView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.context = context;
setOrientation(VERTICAL);
optionStrList = new ArrayList<>();
textViewList = new ArrayList<>();
optionViewList = new ArrayList<>();
}
public void setOptionStrList(List<String> optionStrList) {
this.optionStrList = optionStrList;
optionViewList.clear();
creat();
}
public void setViewLists(List<View> viewLists, List<TextView> textViewLists) {
this.optionViewList = viewLists;
this.textViewList = textViewLists;
clearViewLists();
creat();
}
public void startSelect() {
isSelect = true;
updataView();
}
public void stopSelect() {
isSelect = false;
updataView();
}
public void seletePosition(int position) {
select = position;
updataView();
}
public int getSelect() {
return select;
}
public String getSelectStr() {
return optionStrList.get(select);
}
public void setListener(viewOnClickListener listener) {
this.listener = listener;
}
private void creat() {
if (optionStrList == null) {
Log.e(TAG, "stringList is null.");
return;
}
post(new Runnable() {
@Override
public void run() {
//计算实际可设置布局宽度
calculateWidth();
//生成相应的数据
creatData();
//计算行数
calculateRow();
//生成view
creatView();
}
});
}
private void clearViewLists() {
//复用相关,清除view
for (View view : optionViewList) {
ViewGroup group = ((ViewGroup) view.getParent());
if (group != null) {
group.removeView(view);
}
}
}
/**
* 计算实际宽度扣除左右两边的padding
*/
private void calculateWidth() {
Width = getWidth();
if (Width == 0) {
measure(0, 0);
Width = getMeasuredWidth();
}
Width -= getPaddingLeft();
Width -= getPaddingRight();
}
private void creatData() {
if (optionViewList.size() == 0) {
for (String str : optionStrList) {
bulidView(str);
}
}
}
private void calculateRow() {
List<Integer> viewWidths = new ArrayList<>();
//超过限制宽度强制设为最大
for (View view : optionViewList) {
int width = view.getWidth();
if (width == 0) {
view.measure(0, 0);
width = view.getMeasuredWidth();
}
if (width > Width) {
width = Width;
}
viewWidths.add(width);
}
//计算需要生成的行数 每行需要放置多少个view
rowList = new ArrayList<>();
//记录加到第几个view
int size = 0;
for (int i = 0; i < viewWidths.size(); i++) {
rowList.add(new ArrayList<View>());
int futureWidth = 0;
for (int j = size; j < optionViewList.size(); j++) {
//计算加上下次宽度是否超过屏幕宽度
//超过跳出for
futureWidth += viewWidths.get(j);
if (futureWidth <= Width) {
rowList.get(i).add(optionViewList.get(j));
//设置点击事件
setOnclick(j, optionViewList.get(j));
} else {
break;
}
}
//记录加到第几个view
size += rowList.get(i).size();
//判断是否全部view加载完毕
if (size >= optionViewList.size()) {
break;
}
}
}
private void creatView() {
//遍历生成LinearLayout行数
removeAllViews();
for (List<View> list : rowList) {
LinearLayout linearLayout = new LinearLayout(context);
for (View view : list) {
linearLayout.addView(view);
}
addView(linearLayout);
}
seletePosition(select);
}
private void bulidView(String str) {
View view = LayoutInflater.from(context).inflate(R.layout.tv_str, this, false);
TextView textView = view.findViewById(R.id.tv);
textView.setText(str);
textViewList.add(textView);
optionViewList.add(view);
}
private void updataView() {
if (isSelect) {
for (TextView textView : textViewList) {
textView.setBackgroundResource(R.drawable.str_b);
}
textViewList.get(select).setBackgroundResource(R.drawable.str_a);
}
}
private void setOnclick(final int position, final View view) {
view.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
seletePosition(position);
if (listener != null) {
listener.onClick(position, textViewList.get(position));
}
}
});
}
public interface viewOnClickListener {
void onClick(int position, TextView textView);
}
}
使用的相关xml
tv_str.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="@+id/tv"
android:layout_margin="5dp"
android:padding="5dp"
android:singleLine="true"
android:background="@drawable/str_b"
android:textColor="@android:color/white"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="" />
</FrameLayout>
str_a.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="10dp" />
<solid android:color="@android:color/holo_blue_bright" />
</shape>
str_b.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="10dp" />
<solid android:color="@android:color/darker_gray" />
</shape>
补充说明
想要修改itme的样式只要在bulidView
中修改view就好了。
顺便需要记得设置textViewList
这个list,这边是用这个list和viewlist进行绑定的,其实可以使用map进行比较精确的绑定,但是我这边直接使用了list。
总结
一个简单的自定义记录!