今天做项目碰到一个搜索界面的绘制,包括历史记录的添加,搜索后添加到搜索列表,显示的样式类似淘宝 的搜索记录
1.首先创建FlowLayout类,继承ViewGroup
public class FlowLayout extends ViewGroup { private static final String TAG = "FlowLayout"; private static final int LEFT = -1; private static final int CENTER = 0; private static final int RIGHT = 1; protected List<List<View>> mAllViews = new ArrayList<List<View>>(); protected List<Integer> mLineHeight = new ArrayList<Integer>(); protected List<Integer> mLineWidth = new ArrayList<Integer>(); private int mGravity; private List<View> lineViews = new ArrayList<>(); public FlowLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.TagFlowLayout); mGravity = ta.getInt(R.styleable.TagFlowLayout_gravity, LEFT); ta.recycle(); } 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; int cCount = getChildCount(); for (int i = 0; i < cCount; i++) { View child = getChildAt(i); if (child.getVisibility() == View.GONE) { if (i == cCount - 1) { width = Math.max(lineWidth, width); height += lineHeight; } continue; } measureChild(child, widthMeasureSpec, heightMeasureSpec); MarginLayoutParams lp = (MarginLayoutParams) child .getLayoutParams(); int childWidth = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin; int childHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin; if (lineWidth + childWidth > sizeWidth - getPaddingLeft() - getPaddingRight()) { width = Math.max(width, lineWidth); lineWidth = childWidth; height += lineHeight; lineHeight = childHeight; } else { lineWidth += childWidth; lineHeight = Math.max(lineHeight, childHeight); } if (i == cCount - 1) { width = Math.max(lineWidth, width); height += lineHeight; } } setMeasuredDimension( // modeWidth == MeasureSpec.EXACTLY ? sizeWidth : width + getPaddingLeft() + getPaddingRight(), modeHeight == MeasureSpec.EXACTLY ? sizeHeight : height + getPaddingTop() + getPaddingBottom()// ); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { mAllViews.clear(); mLineHeight.clear(); mLineWidth.clear(); lineViews.clear(); int width = getWidth(); int lineWidth = 0; int lineHeight = 0; int cCount = getChildCount(); for (int i = 0; i < cCount; i++) { View child = getChildAt(i); if (child.getVisibility() == View.GONE) continue; MarginLayoutParams lp = (MarginLayoutParams) child .getLayoutParams(); int childWidth = child.getMeasuredWidth(); int childHeight = child.getMeasuredHeight(); if (childWidth + lineWidth + lp.leftMargin + lp.rightMargin > width - getPaddingLeft() - getPaddingRight()) { mLineHeight.add(lineHeight); mAllViews.add(lineViews); mLineWidth.add(lineWidth); lineWidth = 0; lineHeight = childHeight + lp.topMargin + lp.bottomMargin; lineViews = new ArrayList<View>(); } lineWidth += childWidth + lp.leftMargin + lp.rightMargin; lineHeight = Math.max(lineHeight, childHeight + lp.topMargin + lp.bottomMargin); lineViews.add(child); } mLineHeight.add(lineHeight); mLineWidth.add(lineWidth); mAllViews.add(lineViews); int left = getPaddingLeft(); int top = getPaddingTop(); int lineNum = mAllViews.size(); for (int i = 0; i < lineNum; i++) { lineViews = mAllViews.get(i); lineHeight = mLineHeight.get(i); // set gravity int currentLineWidth = this.mLineWidth.get(i); switch (this.mGravity) { case LEFT: left = getPaddingLeft(); break; case CENTER: left = (width - currentLineWidth) / 2 + getPaddingLeft(); break; case RIGHT: left = width - currentLineWidth + getPaddingLeft(); break; } 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(); child.layout(lc, tc, rc, bc); left += child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin; } top += lineHeight; } } @Override public LayoutParams generateLayoutParams(AttributeSet attrs) { return new MarginLayoutParams(getContext(), attrs); } @Override protected LayoutParams generateDefaultLayoutParams() { return new MarginLayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); } @Override protected LayoutParams generateLayoutParams(LayoutParams p) { return new MarginLayoutParams(p); } }
2.然后创建TagFlowLayout,继承FlowLayout
public class TagFlowLayout extends FlowLayout implements TagAdapter.OnDataChangedListener { private TagAdapter mTagAdapter; private boolean mAutoSelectEffect = true; private int mSelectedMax = -1;//-1为不限制数量 private static final String TAG = "TagFlowLayout"; private MotionEvent mMotionEvent; private Set<Integer> mSelectedView = new HashSet<Integer>(); public TagFlowLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.TagFlowLayout); mAutoSelectEffect = ta.getBoolean(R.styleable.TagFlowLayout_auto_select_effect, true); mSelectedMax = ta.getInt(R.styleable.TagFlowLayout_max_select, -1); ta.recycle(); if (mAutoSelectEffect) { setClickable(true); } } public TagFlowLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } public TagFlowLayout(Context context) { this(context, null); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int cCount = getChildCount(); for (int i = 0; i < cCount; i++) { TagView tagView = (TagView) getChildAt(i); if (tagView.getVisibility() == View.GONE) continue; if (tagView.getTagView().getVisibility() == View.GONE) { tagView.setVisibility(View.GONE); } } super.onMeasure(widthMeasureSpec, heightMeasureSpec); } public interface OnSelectListener { void onSelected(Set<Integer> selectPosSet); } private OnSelectListener mOnSelectListener; public void setOnSelectListener(OnSelectListener onSelectListener) { mOnSelectListener = onSelectListener; if (mOnSelectListener != null) setClickable(true); } public interface OnTagClickListener { boolean onTagClick(View view, int position, FlowLayout parent); } private OnTagClickListener mOnTagClickListener; public void setOnTagClickListener(OnTagClickListener onTagClickListener) { mOnTagClickListener = onTagClickListener; if (onTagClickListener != null) setClickable(true); } public void setAdapter(TagAdapter adapter) { mTagAdapter = adapter; mTagAdapter.setOnDataChangedListener(this); mSelectedView.clear(); changeAdapter(); } private void changeAdapter() { removeAllViews(); TagAdapter adapter = mTagAdapter; TagView tagViewContainer = null; HashSet preCheckedList = mTagAdapter.getPreCheckedList(); for (int i = 0; i < adapter.getCount(); i++) { View tagView = adapter.getView(this, i, adapter.getItem(i)); tagViewContainer = new TagView(getContext()); // ViewGroup.MarginLayoutParams clp = (ViewGroup.MarginLayoutParams) tagView.getLayoutParams(); // ViewGroup.MarginLayoutParams lp = new ViewGroup.MarginLayoutParams(clp); // lp.width = ViewGroup.LayoutParams.WRAP_CONTENT; // lp.height = ViewGroup.LayoutParams.WRAP_CONTENT; // lp.topMargin = clp.topMargin; // lp.bottomMargin = clp.bottomMargin; // lp.leftMargin = clp.leftMargin; // lp.rightMargin = clp.rightMargin; tagView.setDuplicateParentStateEnabled(true); if (tagView.getLayoutParams() != null) { tagViewContainer.setLayoutParams(tagView.getLayoutParams()); } else { ViewGroup.MarginLayoutParams lp = new ViewGroup.MarginLayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); lp.setMargins(dip2px(getContext(), 5), dip2px(getContext(), 5), dip2px(getContext(), 5), dip2px(getContext(), 5)); tagViewContainer.setLayoutParams(lp); } tagViewContainer.addView(tagView); addView(tagViewContainer); if (preCheckedList.contains(i)) { tagViewContainer.setChecked(true); } if (mTagAdapter.setSelected(i, adapter.getItem(i))) { mSelectedView.add(i); tagViewContainer.setChecked(true); } } mSelectedView.addAll(preCheckedList); } @Override public boolean onTouchEvent(MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_UP) { mMotionEvent = MotionEvent.obtain(event); } return super.onTouchEvent(event); } @Override public boolean performClick() { if (mMotionEvent == null) return super.performClick(); int x = (int) mMotionEvent.getX(); int y = (int) mMotionEvent.getY(); mMotionEvent = null; TagView child = findChild(x, y); int pos = findPosByView(child); if (child != null) { doSelect(child, pos); if (mOnTagClickListener != null) { return mOnTagClickListener.onTagClick(child.getTagView(), pos, this); } } return true; } public void setMaxSelectCount(int count) { if (mSelectedView.size() > count) { Log.w(TAG, "you has already select more than " + count + " views , so it will be clear ."); mSelectedView.clear(); } mSelectedMax = count; } public Set<Integer> getSelectedList() { return new HashSet<Integer>(mSelectedView); } private void doSelect(TagView child, int position) { if (mAutoSelectEffect) { if (!child.isChecked()) { //处理max_select=1的情况 if (mSelectedMax == 1 && mSelectedView.size() == 1) { Iterator<Integer> iterator = mSelectedView.iterator(); Integer preIndex = iterator.next(); TagView pre = (TagView) getChildAt(preIndex); pre.setChecked(false); child.setChecked(true); mSelectedView.remove(preIndex); mSelectedView.add(position); } else { if (mSelectedMax > 0 && mSelectedView.size() >= mSelectedMax) return; child.setChecked(true); mSelectedView.add(position); } } else { child.setChecked(false); mSelectedView.remove(position); } if (mOnSelectListener != null) { mOnSelectListener.onSelected(new HashSet<Integer>(mSelectedView)); } } } public TagAdapter getAdapter() { return mTagAdapter; } private static final String KEY_CHOOSE_POS = "key_choose_pos"; private static final String KEY_DEFAULT = "key_default"; @Override protected Parcelable onSaveInstanceState() { Bundle bundle = new Bundle(); bundle.putParcelable(KEY_DEFAULT, super.onSaveInstanceState()); String selectPos = ""; if (mSelectedView.size() > 0) { for (int key : mSelectedView) { selectPos += key + "|"; } selectPos = selectPos.substring(0, selectPos.length() - 1); } bundle.putString(KEY_CHOOSE_POS, selectPos); return bundle; } @Override protected void onRestoreInstanceState(Parcelable state) { if (state instanceof Bundle) { Bundle bundle = (Bundle) state; String mSelectPos = bundle.getString(KEY_CHOOSE_POS); if (!TextUtils.isEmpty(mSelectPos)) { String[] split = mSelectPos.split("\\|"); for (String pos : split) { int index = Integer.parseInt(pos); mSelectedView.add(index); TagView tagView = (TagView) getChildAt(index); if (tagView != null) tagView.setChecked(true); } } super.onRestoreInstanceState(bundle.getParcelable(KEY_DEFAULT)); return; } super.onRestoreInstanceState(state); } private int findPosByView(View child) { final int cCount = getChildCount(); for (int i = 0; i < cCount; i++) { View v = getChildAt(i); if (v == child) return i; } return -1; } private TagView findChild(int x, int y) { final int cCount = getChildCount(); for (int i = 0; i < cCount; i++) { TagView v = (TagView) getChildAt(i); if (v.getVisibility() == View.GONE) continue; Rect outRect = new Rect(); v.getHitRect(outRect); if (outRect.contains(x, y)) { return v; } } return null; } @Override public void onChanged() { mSelectedView.clear(); changeAdapter(); } public static int dip2px(Context context, float dpValue) { final float scale = context.getResources().getDisplayMetrics().density; return (int) (dpValue * scale + 0.5f); } }
3.接着就是创建TagView,用于一些操作
public class TagView extends FrameLayout implements Checkable { private boolean isChecked; private static final int[] CHECK_STATE = new int[]{android.R.attr.state_checked}; public TagView(Context context) { super(context); } public View getTagView() { return getChildAt(0); } @Override public int[] onCreateDrawableState(int extraSpace) { int[] states = super.onCreateDrawableState(extraSpace + 1); if (isChecked()) { mergeDrawableStates(states, CHECK_STATE); } return states; } /** * Change the checked state of the view * * @param checked The new checked state */ @Override public void setChecked(boolean checked) { if (this.isChecked != checked) { this.isChecked = checked; refreshDrawableState(); } } /** * @return The current checked state of the view */ @Override public boolean isChecked() { return isChecked; } /** * Change the checked state of the view to the inverse of its current state */ @Override public void toggle() { setChecked(!isChecked); } }
4.最后就是去使用它了
首先是布局文件
<ScrollView android:scrollbars="none" android:layout_width="match_parent" android:layout_height="wrap_content"> <LinearLayout android:orientation="vertical" android:layout_width="match_parent" android:layout_height="wrap_content"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <TextView android:text="历史搜索" android:textSize="16sp" android:textColor="@color/gray_66" android:layout_marginLeft="15dp" android:layout_marginTop="25dp" android:layout_width="0dp" android:layout_weight="1" android:layout_height="wrap_content" /> <ImageView android:id="@+id/iv_search_history" android:layout_marginTop="29dp" android:src="@mipmap/flow_delete" android:layout_marginBottom="5dp" android:layout_marginRight="15dp" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout> <com.chinaso.beautifulchina.view.TagFlowLayout android:id="@+id/flow_search_history" android:layout_marginLeft="8dp" android:layout_marginRight="8dp" android:layout_width="match_parent" android:layout_height="wrap_content"/> </ScrollView>
接着就是在activity中或fragment中的应用了
@BindView(R.id.flow_search_history) TagFlowLayout flowSearchHistory;
@SuppressLint("HandlerLeak") public Handler handler = new Handler(){ @Override public void handleMessage(Message msg) { switch (msg.what){ case 1: flowSearchHistory.setAdapter(new TagAdapter<String>(searchHistoryList) { @Override public View getView(FlowLayout parent, int position, String s) { historyTv = new TextView(mContext); ViewGroup.MarginLayoutParams params = new ViewGroup.MarginLayoutParams(ViewGroup.MarginLayoutParams.WRAP_CONTENT, ViewGroup.MarginLayoutParams.WRAP_CONTENT); params.setMargins(10, 10, 10, 8); //搜索记录的上下左右距离 historyTv.setLayoutParams(params); historyTv.setBackground(getResources().getDrawable(R.drawable.bg_flow_item)); //iawable下的shap文件,自己绘制 historyTv.setTextSize(14); historyTv.setTextColor(getResources().getColor(R.color.black_33)); historyTv.setSingleLine(); //只显示一行 historyTv.setEllipsize(TextUtils.TruncateAt.END); //一行结束后跟上... historyTv.setText(s); return historyTv; } }); break; } super.handleMessage(msg); } };
protected void initData() { super.initData(); searchHistoryList = new ArrayList<>(); //搜索历史集合 searchHistoryList.add("123"); searchHistoryList.add("12312321311"); searchHistoryList.add("111111111111111111"); searchHistoryList.add("11111111111"); searchHistoryList.add("222222222"); handler.sendEmptyMessageDelayed(1, 0); flowSearchHistory.setOnTagClickListener((view, position, parent) ->{ showToast(searchHistoryList.get(position)); return true; }); }
@OnClick({R.id.tv_search, R.id.iv_search_history}) public void onViewClicked(View view) { switch (view.getId()) { case R.id.tv_search: //搜索 flowSearchHistory.setVisibility(View.VISIBLE); //通知handler更新UI if(edSearchContent.length()> 0 && !edSearchContent.getText().toString().trim().equals("")){ searchHistoryList.add(0,edSearchContent.getText().toString()); handler.sendEmptyMessageDelayed(1, 0); }else { showToast("请输入要搜索的内容"); } break; case R.id.iv_search_history: //删除全部搜索历史 if (searchHistoryList != null && searchHistoryList.size() !=0) { chooseDialog = new ChooseDialog(mContext, "确认删除全部记录?", new ChooseDialog.SetDialogClickListener() { @Override public void clickConfirm() { searchHistoryList.clear(); flowSearchHistory.setVisibility(View.GONE); chooseDialog.dismiss(); } @Override public void clickCancel() { chooseDialog.dismiss(); } }); chooseDialog.show(); }else { showToast("暂无记录需要删除"); } break; } }
@Override protected void onDestroy() { super.onDestroy(); //防止使用handler发生内存泄露 handler.removeCallbacksAndMessages(null); }