团队项目——第一阶段冲刺1
一、前言
昨天 | ———————— |
今天 |
1、环境搭建 2、明确任务详情 3、考虑设计思路 4、爱好界面设计 |
遇到的问题 | 标签排列不规整的问题 |
二、分析爱好选择功能
1.多种标签有序排列。0.7h
2.点击标签后有回馈。1.2h
3.传递标签内容。(暂未实现)
3.添加有限标签数(目前暂定最多添加5个标签)。0.5h
三、冲刺成果
设计思路
1.使用FlowLayout流式标签进行布局,使标签排列较为整洁
2.回馈采用点击添加后在上方显示的方法(虽然显得麻烦但比较直观)
3.重复添加标签或超出可添加个数使用 Toast 弹框提示
4.添加自定义标签,用户可按照自己的想法添加。添加采用 Dialog 弹框进行添加。
5.根据上述分析,添加依赖
dependencies { androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' implementation 'com.hyman:flowlayout-lib:1.1.2' }
效果展示
添加标签示例
重复添加提示,选择超出提示
自定义弹框添加
代码展示
1.Dialog 弹窗设计
<style name="dialogstyle"> <!--设置dialog的背景--> <item name="android:windowBackground">@android:color/transparent</item> <!--设置Dialog的windowFrame框为无--> <item name="android:windowFrame">@null</item> <!--设置无标题--> <item name="android:windowNoTitle">true</item> <!--是否浮现在activity之上--> <item name="android:windowIsFloating">true</item> <!--是否半透明--> <item name="android:windowIsTranslucent">true</item> <!--设置窗口内容不覆盖--> <item name="android:windowContentOverlay">@null</item> <!--设置动画,在这里使用让它继承系统的Animation.Dialog--> <item name="android:windowAnimationStyle">@android:style/Animation.Dialog</item> <!--背景是否模糊显示--> <item name="android:backgroundDimEnabled">true</item> </style>
别忘了在AndroidManifest中添加
<activity android:name=".AddTagActivity" android:theme="@style/dialogstyle" />
2.流式标签适配器
public class FlowLayoutAdapter extends BaseAdapter { private Context mContext; private List<String> mList; FlowLayoutAdapter(Context context, List<String> list) { mContext = context; mList = list; } @Override public int getCount() { return mList.size(); } @Override public String getItem(int position) { return mList.get(position); } @Override public long getItemId(int position) { return position; } @SuppressLint("InflateParams") @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder; if (convertView == null) { convertView = LayoutInflater.from(mContext).inflate( R.layout.item_tag, null); holder = new ViewHolder(); holder.mBtnTag = convertView.findViewById(R.id.btn_tag); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } holder.mBtnTag.setText(getItem(position)); return convertView; } static class ViewHolder { Button mBtnTag; } }
3.标签点击事件(包括刷新数据和回传数据)
public class MainActivity extends Activity { private TextView tv_remind; private FlowLayout tcy_my_label, tcy_hot_label; private FlowLayoutAdapter mMyLabelAdapter; private List<String> MyLabelLists, HotLabelLists; private static int TAG_REQUESTCODE = 0x101; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initView(); initData(); } private void initView() { tv_remind = findViewById(R.id.tv_remind); tcy_my_label = findViewById(R.id.tcy_my_label); tcy_hot_label = findViewById(R.id.tcy_hot_label); } private void initData() { String[] date = getResources().getStringArray(R.array.tags); HotLabelLists = new ArrayList<>(); Collections.addAll(HotLabelLists, date); FlowLayoutAdapter mHotLabelAdapter = new FlowLayoutAdapter(this, HotLabelLists); tcy_hot_label.setAdapter(mHotLabelAdapter); tcy_hot_label.setItemClickListener(new TagCloudLayoutItemOnClick(1)); MyLabelLists = new ArrayList<>(); mMyLabelAdapter = new FlowLayoutAdapter(this, MyLabelLists); tcy_my_label.setAdapter(mMyLabelAdapter); tcy_my_label.setItemClickListener(new TagCloudLayoutItemOnClick(0)); String labels = String.valueOf(getIntent().getStringExtra("labels")); if (!TextUtils.isEmpty(labels) && labels.length() > 0 && !labels.equals("null")) { String[] temp = labels.split(","); Collections.addAll(MyLabelLists, temp); ChangeMyLabels(); } } //刷新我的标签数据 private void ChangeMyLabels() { tv_remind.setVisibility(MyLabelLists.size() > 0 ? View.GONE : View.VISIBLE); tcy_my_label.setVisibility(MyLabelLists.size() > 0 ? View.VISIBLE : View.GONE); mMyLabelAdapter.notifyDataSetChanged(); } //标签的点击事件 class TagCloudLayoutItemOnClick implements FlowLayout.TagItemClickListener { int index; TagCloudLayoutItemOnClick(int index) { this.index = index; } @Override public void itemClick(int position) { switch (index) { case 0: MyLabelLists.remove(MyLabelLists.get(position)); ChangeMyLabels(); break; case 1: if (MyLabelLists.size() < 5) { if (HotLabelLists.get(position).equals("自定义")) { startActivityForResult( new Intent(MainActivity.this, AddTagActivity.class), TAG_REQUESTCODE); } else { Boolean isExits = isExist(MyLabelLists, HotLabelLists.get(position)); if (isExits) { Toast.makeText(MainActivity.this, "此标签已经添加啦", Toast.LENGTH_LONG).show(); return; } MyLabelLists.add(HotLabelLists.get(position)); ChangeMyLabels(); } } else { Toast.makeText(MainActivity.this, "最多只能添加5个标签", Toast.LENGTH_LONG).show(); } break; default: break; } } } //将数组里面的字符串遍历一遍,看是否存在相同标签 public static Boolean isExist(List<String> str, String compareStr) { boolean isExist = false;//默认沒有相同标签 for (int i = 0; i < str.size(); i++) { if (compareStr.equals(str.get(i))) { isExist = true; } } return isExist; } //回传数据 @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (TAG_REQUESTCODE == requestCode) { if (resultCode == AddTagActivity.TAG_RESULTCODE) { String label = data.getStringExtra("tags"); MyLabelLists.add(label); ChangeMyLabels(); } } } }
4.标签添加(点击后的回馈)
public class AddTagActivity extends Activity implements View.OnClickListener { private EditText mEtLabel; public final static int TAG_RESULTCODE = 0x102; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_add_tag); initView(); initData(); } private void initData() { //根据输入框输入值的改变提示最大允许输入的个数 mEtLabel.addTextChangedListener(new TextWatcher_Enum()); } private void initView() { mEtLabel = findViewById(R.id.et_label); Button mBtnSure = findViewById(R.id.btn_sure); mBtnSure.setOnClickListener(this); } @Override public void onClick(View v) { if (v.getId() == R.id.btn_sure) { String label = mEtLabel.getText().toString(); if (TextUtils.isEmpty(label)) { Toast.makeText(AddTagActivity.this, "自定义标签不应为空", Toast.LENGTH_LONG).show(); return; } Intent intent = getIntent(); intent.putExtra("tags", label); setResult(TAG_RESULTCODE, intent); finish(); } } //根据输入框输入值的长度超过8个字符的时候,弹出输入的标签应控制在8个字 class TextWatcher_Enum implements TextWatcher { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { int lenght = mEtLabel.getText().toString().trim().length(); if (lenght > 8) { Toast.makeText(AddTagActivity.this, "输入的标签应控制在8个字", Toast.LENGTH_LONG).show(); } } @Override public void afterTextChanged(Editable s) { } } }
5.定义标签样式
<?xml version="1.0" encoding="utf-8"?> <resources> <!--每个item纵向间距--> <attr name="verticalSpacing" format="dimension" /> <!-- 每个item横向间距--> <attr name="horizontalSpacing" format="dimension" /> <declare-styleable name="FlowLayout"> <attr name="verticalSpacing" /> <attr name="horizontalSpacing" /> </declare-styleable> </resources>
调用上面xml代码来规定各个标签之间距离
public class FlowLayout extends ViewGroup { //每个item纵向间距 private int mVerticalSpacing; //每个item横向间距 private int mHorizontalSpacing; private BaseAdapter mAdapter; private TagItemClickListener mListener; private DataChangeObserver mObserver; public FlowLayout(Context context) { this(context, null); } public FlowLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } public FlowLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); //获取自定义样式属性 TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.FlowLayout, defStyle, 0); for (int i = 0; i < a.getIndexCount(); i++) { int attr = a.getIndex(i); switch (attr) { case R.styleable.FlowLayout_verticalSpacing: mVerticalSpacing = a.getDimensionPixelSize(R.styleable.FlowLayout_verticalSpacing, 5); break; case R.styleable.FlowLayout_horizontalSpacing: mHorizontalSpacing = a.getDimensionPixelSize(R.styleable.FlowLayout_horizontalSpacing, 10); break; } } a.recycle(); } //负责设置子控件的测量模式和大小 根据所有子控件设置自己的宽和高 @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // 获得此ViewGroup上级容器为其推荐的宽和高,以及计算模式 int heighMode = MeasureSpec.getMode(heightMeasureSpec); int heighSize = MeasureSpec.getSize(heightMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); //高 int height = 0; // 每一行的高度,累加至height int lineHeight = 0; // 在warp_content情况下,记录当前childView的左边的一个位置 int childLeft = getPaddingLeft(); // 在warp_content情况下,记录当前childView的上边的一个位置 int childTop = getPaddingTop(); // getChildCount得到子view的数目,遍历循环出每个子View for (int i = 0; i < getChildCount(); i++) { //拿到index上的子view View childView = getChildAt(i); // 测量每一个child的宽和高 measureChild(childView, widthMeasureSpec, heightMeasureSpec); //当前子空间实际占据的高度 int childHeight = childView.getMeasuredHeight(); //当前子空间实际占据的宽度 int childWidth = childView.getMeasuredWidth(); lineHeight = Math.max(childHeight, lineHeight);// 取最大值 //如果加入当前childView,超出最大宽度,则将目前最大宽度给width,类加height 然后开启新行 if (childWidth + childLeft + getPaddingRight() > widthSize) { childLeft = getPaddingLeft();// 重新开启新行,开始记录childLeft childTop += mVerticalSpacing + childHeight;// 叠加当前的高度 lineHeight = childHeight;// 开启记录下一行的高度 } else { //否则累加当前childView的宽度 childLeft += childWidth + mHorizontalSpacing; } } height += childTop + lineHeight + getPaddingBottom(); setMeasuredDimension(widthSize, heighMode == MeasureSpec.EXACTLY ? heighSize : height); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int width = r - l; int childLeft = getPaddingLeft(); int childTop = getPaddingTop(); int lineHeight = 0; //遍历所有childView根据其宽和高,计算子控件应该出现的位置 for (int i = 0; i < getChildCount(); i++) { final View childView = getChildAt(i); if (childView.getVisibility() == View.GONE) { continue; } int childWidth = childView.getMeasuredWidth(); int childHeight = childView.getMeasuredHeight(); lineHeight = Math.max(childHeight, lineHeight); // 如果已经需要换行 if (childLeft + childWidth + getPaddingRight() > width) { childLeft = getPaddingLeft(); childTop += mVerticalSpacing + lineHeight; lineHeight = childHeight; } childView.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight); childLeft += childWidth + mHorizontalSpacing; } } private void drawLayout() { if (mAdapter == null || mAdapter.getCount() == 0) { return; } removeAllViews(); for (int i = 0; i < mAdapter.getCount(); i++) { View view = mAdapter.getView(i, null, null); final int position = i; view.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (mListener != null) { mListener.itemClick(position); } } }); addView(view); } } public void setAdapter(BaseAdapter adapter) { if (mAdapter == null) { mAdapter = adapter; if (mObserver == null) { mObserver = new DataChangeObserver(); mAdapter.registerDataSetObserver(mObserver); } drawLayout(); } } public void setItemClickListener(TagItemClickListener mListener) { this.mListener = mListener; } public interface TagItemClickListener { void itemClick(int position); } class DataChangeObserver extends DataSetObserver { @Override public void onChanged() { drawLayout(); } @Override public void onInvalidated() { super.onInvalidated(); } } }