目录
一丶概述
二丶Layout布局
三丶Activity
四丶最重要的Adapter了
五丶说点其他的
最近遇到一个需求,如下面的图所示。
功能可能并不难,就是实现一个添加标签,涉及到流式布局,很多人都知道怎么做,但是作为刚入行不久的我还是稍微有点难度,所以决定写来记录下自己的实现方式,其实连这都是请教了朋友才完成的,他帮我完成了一大部分。这里除了记录还得感谢我的这位朋友。当然,肯定有很多很多更好的方式有待我日后慢慢去学习。小生知识微薄,所以只能使用最笨的方式。还请见谅。不足之处,还望大家多多指点。
因为需求是怕以后会有改动,所以我们要做成动态的列表去展示。根据图来可以看成有n个类型,没个类型里面有很多元素。并且最后一项是其他标签,可以多选,其他只能单选。博主其实刚开始想用三方FlowTagLayout来做的,可是做完过后发现有很多地方并不满足,所以就放弃了,可能是因为博主学艺不精吧。
最后博主决定使用RecyclerView来做,RecyclerView的item使用自定义RedioGroup,然后addview就好了。不废话了代码:
外层布局,其实没有什么,就是一个RecyclerView。
接下里是item的布局:
item的布局我就使用了一个自定义的FlowRadioGroup,代码在下面。
FlowRadioGroup:里面注视已经写好了。
public class FlowRadioGroup extends RadioGroup {
public FlowRadioGroup(Context context) {
super(context);
}
public FlowRadioGroup(Context context, AttributeSet attrs) {
super(context, attrs);
}
/**
* 储存所有的view 按行记录
*/
private List> mAllViews = new ArrayList>();
/**
* 记录每一行的高度
*/
private List mLineHeight = new ArrayList();
private String TAG = "TAG";
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 置空 view 容器 和 lineHeight 容器 重新赋值
//因为OnMeasure方法会走两次,第一次是实例化这个对象的时候高度和宽度都是0
//之后走了OnSizeChange()方法后 又走了一次OnMeasure,所以要把第一次加进去的数据清空。
mAllViews.clear();
mLineHeight.clear();
//得到上级容器为其推荐的宽高和计算模式
int specWidthMode = MeasureSpec.getMode(widthMeasureSpec);
int specHeighMode = MeasureSpec.getMode(heightMeasureSpec);
int specWidthSize = MeasureSpec.getSize(widthMeasureSpec);
int specHeighSize = MeasureSpec.getSize(heightMeasureSpec);
// 计算出所有的 child 的 宽和高
// measureChildren(specWidthSize, specHeighSize);
// 记录如果是 warp_content 是设置的宽和高
int width = 0;
int height = 0;
// 得到子view的个数
int cCount = getChildCount();
/**
* 记录每一行的宽度,width不断取最大宽度
*/
int lineWidth = 0;
/**
* 每一行的高度,累加至height
*/
int lineHeight = 0;
// 存储每一行所有的childView
List lineViews = new ArrayList();
for (int i = 0; i < cCount; i++) {
// 得到每个子View
View child = getChildAt(i);
// 测量每个子View的宽高
measureChild(child, widthMeasureSpec, heightMeasureSpec);
// 当前子view的lp
MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
// 子view的宽和高
int cWidth = 0;
int cheight = 0;
// 当前子 view 实际占的宽
cWidth = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
// 当前子View 实际占的高
cheight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
lineHeight=cheight;
// 需要换行
if(lineWidth + cWidth > specWidthSize){
width = Math.max(lineWidth, cWidth);// 取最大值
lineWidth = cWidth; // 开启新行的时候重新累加width
// 开启新行时累加 height
// lineHeight = cheight; // 记录下一行的高度
mAllViews.add(lineViews);
mLineHeight.add(cheight);
lineViews = new ArrayList<>();
// 换行的时候把该 view 放进 集合里
lineViews.add(child);// 这个 view(child) 是下一行的第一个view
height += cheight; //每个View高度是一样的,直接累加
Log.e("需要换行", "hight--" + height);
Log.e("onMeasure", "AllViews.size() -- > " + mAllViews.size());
}else {
// 不需要换行
lineWidth += cWidth;//
Log.e("不需要换行","hight--"+height);
// 不需要换行时 把子View add 进集合
lineViews.add(child);
}
if(i == cCount-1){
// 如果是最后一个view
width = Math.max(lineWidth, cWidth);
height += cheight;
Log.e("最后一个view","hight--"+height);
}
}
// 循环结束后 把最后一行内容add进集合中
mLineHeight.add(lineHeight); // 记录最后一行
mAllViews.add(lineViews);
// MeasureSpec.EXACTLY 表示设置了精确的值
// 如果 mode 是 MeasureSpec.EXACTLY 时候,则不是 warp_content 用计算来的值,否则则用上级布局分给它的值
setMeasuredDimension(
specWidthMode == MeasureSpec.EXACTLY ? specWidthSize : width,
specHeighMode == MeasureSpec.EXACTLY ? specHeighSize : height
);
Log.e("onMeasure", "mAllViews.size() -- > " + mAllViews.size() + " mLineHeight.size() -- > " + mLineHeight.size() + "Height -- > "+height);
}
/**
* 所有childView的位置的布局
*/
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// 当前行的最大高度
int lineHeight = 0;
// 存储每一行所有的childView
List lineViews = new ArrayList();
int left = 0;
int top = 0;
// 得到总行数
int lineNums = mAllViews.size();
for (int i = 0; i < lineNums; i++)
{
// 每一行的所有的views
lineViews = mAllViews.get(i);
// 当前行的最大高度
lineHeight = mLineHeight.get(i);
Log.e("onLayout" , "第" + i + "行 :" + lineViews.size()+"-------lineHeight"+ lineHeight);
// 遍历当前行所有的View
for (int j = 0; j < lineViews.size(); j++)
{
View child = lineViews.get(j);
if (child.getVisibility() == View.GONE)
{
continue;
}
MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
//计算childView的left,top,right,bottom
int lc = left + lp.leftMargin;
int tc = top + lp.topMargin;
int rc =lc + child.getMeasuredWidth();
int bc = tc + child.getMeasuredHeight();
child.layout(lc, tc, rc, bc);
left += child.getMeasuredWidth() + lp.rightMargin + lp.leftMargin;
}
left = 0;
top += lineHeight;
}
Log.v("onLayout", "onLayout mAllViews.size() -- > " + mAllViews.size() + " mLineHeight.size() -- > "+ mLineHeight.size());
}
/**
* 这个一定要设置,否则会包强转错误
* 设置它支持 marginLayoutParams
*/
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return (LayoutParams) new MarginLayoutParams(getContext(),attrs);
}
}
TagActivity 就是我的主Activity,主要是接受adapter回调选中的数据进行操作和获取数据。
public class NewTagActivity extends BaseActivity implements AddlabelView {
private RecyclerView mTagRecyclerView;
private AddLabelPresenter presenter;
private NewTagRecyclerViewAdapter mTagAdapter;
@Override
protected int initContentView() {
return R.layout.new_tag;
}
@Override
protected void setStatus() {
}
@Override
protected String getToolbarTitle() {
return null;
}
@Override
protected void initUi() {
mTagRecyclerView = findViewById(R.id.tag_recyclerView);
findViewById(R.id.save_label).setOnClickListener(this);
mTagRecyclerView.setLayoutManager(new LinearLayoutManager(this));
mTagRecyclerView.addItemDecoration(new SpaceItemDecoration(10));
mTagAdapter = new NewTagRecyclerViewAdapter(this);
mTagRecyclerView.setAdapter(mTagAdapter);
initData();
}
private void initData() {
presenter = new AddLabelPresenter(this);
// 这里是adapter定义的回调,会把选中的标签返回到acticity以便进行操作
presenter.getLabelDataBean(this);
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.save_label:
saveLabel();
break;
}
}
// 点击保存后的操作
private void saveLabel() {
List allSelectedSubBean = mTagAdapter.getAllSelectedSubBean();
Log.e("zjb", "saveLabel: "+allSelectedSubBean.size());
for (ClickLabelDataBean.SubLabelBean subLabelBean : allSelectedSubBean) {
Log.e("zjb", "saveLabel: "+subLabelBean.getLabel_name());
}
}
// 这里是数据,我用的是mvp模式,比较懒,也就没有去搞专门的测试数据了。这个不重要
@Override
public void showLabelDateBean(List dataBeanList) {
// 因为跟后台商量好了最后一个会返回其它这个类型,这里也set也是为了标记下当是最后一个集合的时候就是多选
dataBeanList.get(dataBeanList.size() - 1).setLabel_type(1);
mTagAdapter.setDatas(dataBeanList);
}
}
最关键的就是adapter了 不墨迹 我直接上代码。 代码里面有注视,尽力看注释吧。
public class NewTagRecyclerViewAdapter extends RecyclerView.Adapter {
private static final int MAX_MUXSLECTE_LIMIT_SIZE = 5;
private Context mContext;
private int [] img = {R.mipmap.excavate_spot,R.mipmap.intention_spot,R.mipmap.other_spot,
R.mipmap.relationship_spot,R.mipmap.trust_spot};
private List mLabelBeanList = new ArrayList<>();
private Map> mAllSelectedSubBean = new HashMap<>();
public NewTagRecyclerViewAdapter(Context context) {
this.mContext = context;
}
public void addDatas(List datas){
this.mLabelBeanList.addAll(datas);
notifyDataSetChanged();
}
public void clearDatas(){
this.mLabelBeanList.clear();
notifyDataSetChanged();
}
public void setDatas(List datas){
this.mLabelBeanList.clear();
addDatas(datas);
Toast.makeText(mContext, datas.size(), Toast.LENGTH_SHORT).show();
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_tag, parent, false);
return new MyViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
MyViewHolder myViewHolder = (MyViewHolder) holder;
ClickLabelDataBean subLabelBean = mLabelBeanList.get(position);
//ui
myViewHolder.mTvName.setText(subLabelBean.getLabel_name());
myViewHolder.mIvName.setImageResource(img[position]);
//暂存选中数据
List eachLineSelectDatas = new ArrayList<>();
String label_id = subLabelBean.getLabel_id();
mAllSelectedSubBean.put(label_id, eachLineSelectDatas);
final List subLabel = subLabelBean.getSubLabel();
if(subLabelBean.getLabel_type() == 0) {// 是单选选项
handleSubSingleItemClick(myViewHolder.mRadioGroup, eachLineSelectDatas, label_id);
for (ClickLabelDataBean.SubLabelBean labelBean : subLabel) {
RadioButton radioButton = new RadioButton(mContext);
myViewHolder.mRadioGroup.addView(radioButton);
radioButton.setId(radioButton.hashCode());
radioButton.setText(labelBean.getLabel_name());
radioButton.setTag(labelBean);
if("1".equals(labelBean.getChoose())){
myViewHolder.mRadioGroup.check(radioButton.hashCode());
}
}
}else if(subLabelBean.getLabel_type() == 1){ //多选选项
int size = subLabel.size();
for (int i = 0; i < size; i++) {
ClickLabelDataBean.SubLabelBean labelBean = subLabel.get(i);
if(i == size - 1 ){ //【添加其他标签】
TextView textView = new TextView(mContext);
textView.setText(labelBean.getLabel_name());
textView.setTextSize(20);
handlesubAddOtherTagClick(textView);
myViewHolder.mRadioGroup.addView(textView);
}else {
CheckBox checkBox = new CheckBox(mContext);
handleSubMuxItemClick(checkBox, eachLineSelectDatas, label_id);
checkBox.setText(labelBean.getLabel_name());
checkBox.setTag(labelBean);
if("1".equals(labelBean.getChoose())){
checkBox.setChecked(true);
}
myViewHolder.mRadioGroup.addView(checkBox);
}
}
}
}
//处理【添加其他标签】的点击
private void handlesubAddOtherTagClick(TextView textView) {
textView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ToastUtil.showToast(textView.getText().toString());
}
});
}
// 处理多选的点击
private void handleSubMuxItemClick(CheckBox checkBox, List selectSingleDatas, String label_id) {
checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
ClickLabelDataBean.SubLabelBean subLabelData = (ClickLabelDataBean.SubLabelBean) checkBox.getTag();
if(isChecked){
if(selectSingleDatas.size() >= MAX_MUXSLECTE_LIMIT_SIZE){
ToastUtil.showToast(R.string.max_muxselcted_size);
checkBox.setChecked(false);
return;
}
selectSingleDatas.add(subLabelData);
}else {
selectSingleDatas.remove(subLabelData);
}
mAllSelectedSubBean.put(label_id, selectSingleDatas);
}
});
}
//处理单选的点击
private void handleSubSingleItemClick(FlowRadioGroup mRadioGroup, List selectMuxDatas, String label_id) {
mRadioGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
RadioButton radioButton = mRadioGroup.findViewById(checkedId);
ClickLabelDataBean.SubLabelBean subLabelBean = (ClickLabelDataBean.SubLabelBean) radioButton.getTag();
selectMuxDatas.clear();
selectMuxDatas.add(subLabelBean);
mAllSelectedSubBean.put(label_id, selectMuxDatas);
}
});
}
@Override
public int getItemCount() {
return mLabelBeanList.size();
}
static class MyViewHolder extends RecyclerView.ViewHolder{
FlowRadioGroup mRadioGroup;
ImageView mIvName;
TextView mTvName;
MyViewHolder(View itemView) {
super(itemView);
mRadioGroup = (FlowRadioGroup) itemView.findViewById(R.id.flowRadioGroup);
mIvName = (ImageView) itemView.findViewById(R.id.im_name);
mTvName = (TextView) itemView.findViewById(R.id.t_name);
}
}
//提供给外部获取全部选中的结果
List getAllSelectedSubBean() {
List datas = new ArrayList<>();
Set>> entries = mAllSelectedSubBean.entrySet();
for (Map.Entry> entry : entries) {
datas.addAll(entry.getValue());
}
return datas;
}
}
大概基本就是这样了,博主也是第一次写文章,以后会进行优化。希望大家看到的不喜勿喷。我也只是才做了一年多的小人物。只是想记录自己的一点一滴,以后肯定会有更好的实现方式,但是现在肚子里的墨水太少了。写这篇博客也只是为了提醒自己,记录一下而已。如果大家有更好的实现方式,麻烦大家倾囊相授。
祝大家工作顺利,生活愉快。