老规矩,先上图:
功能描述:①用户搜索功能,存本地历史记录;②采用流式布局
存储方式: 采用SharedPreferences
优点:避免使用本地数据库导致项目更新版本升级版本号问题,避免sql常规操作失误引发的bug问题。简单实用
一 布局:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout 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:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/white" android:fitsSystemWindows="false" android:orientation="vertical"> <!--搜索界面false--> <RelativeLayout android:id="@+id/rl_yellow_search" android:layout_width="match_parent" android:layout_height="83dp" android:background="@drawable/bg_title_base"> <!--搜索--> <RelativeLayout android:id="@+id/rl_search" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_marginBottom="@dimen/ds_10dp" android:layout_marginLeft="@dimen/ds_12dp" android:layout_toLeftOf="@+id/tv_search_cancel" android:background="@drawable/bg_white_round_small" android:paddingBottom="@dimen/ds_8dp" android:paddingTop="@dimen/ds_8dp" tools:ignore="RtlHardcoded"> <ImageButton android:id="@+id/btn_gray_search" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_centerVertical="true" android:layout_gravity="center_vertical" android:layout_marginLeft="@dimen/ds_16dp" android:background="@drawable/icon_xiaoxi_sousuo" tools:ignore="ContentDescription,RtlHardcoded" /> <com.yc.stscf.widget.LastInputEditText android:id="@+id/ce_search" android:layout_width="match_parent" android:layout_height="@dimen/ds_30dp" android:layout_centerHorizontal="true" android:layout_centerVertical="true" android:layout_gravity="center_vertical" android:layout_marginRight="@dimen/ds_36dp" android:layout_toRightOf="@+id/btn_gray_search" android:background="@null" android:focusable="false" android:focusableInTouchMode="false" android:inputType="text|number" android:gravity="left|center_horizontal|center_vertical" android:hint="请输入要搜索的关键词" android:imeOptions="actionSearch" android:textColor="@color/cs_999999" android:textColorHint="@color/cs_999999" android:textSize="@dimen/ds_14sp" tools:ignore="HardcodedText,InefficientWeight,NestedWeights,RelativeOverlap,RtlHardcoded,SmallSp" /> <ImageButton android:id="@+id/btn_gray_delete" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_centerVertical="true" android:visibility="gone" android:layout_gravity="center_vertical" android:layout_marginLeft="@dimen/ds_16dp" android:layout_marginRight="@dimen/ds_6dp" android:background="@drawable/icon_xiaoxi_sousuo_delete" android:padding="@dimen/ds_10dp" tools:ignore="ContentDescription,RtlHardcoded" /> </RelativeLayout> <!--取消--> <TextView android:id="@+id/tv_search_cancel" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_alignParentRight="true" android:layout_centerVertical="true" android:layout_marginBottom="@dimen/ds_20dp" android:layout_marginLeft="@dimen/ds_13dp" android:layout_marginRight="@dimen/ds_16dp" android:paddingBottom="@dimen/ds_8dp" android:paddingTop="@dimen/ds_8dp" android:text="取消" android:textColor="@color/white" android:textSize="@dimen/ds_15sp" tools:ignore="HardcodedText,RtlHardcoded" /> </RelativeLayout> <!--无数据--> <RelativeLayout android:id="@+id/rl_no_date" android:layout_width="match_parent" android:layout_height="match_parent" android:visibility="gone"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:drawablePadding="@dimen/ds_30dp" android:drawableTop="@drawable/bd_zanwuxiangguannsousuo" android:gravity="center" android:text="暂无相关搜索" android:textColor="@color/cs_ff000000" android:textSize="@dimen/ds_14sp" tools:ignore="HardcodedText" /> </RelativeLayout> <!--1有数据本地历史的--> <LinearLayout android:id="@+id/ll_history" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <!--History搜索--> <LinearLayout android:layout_width="match_parent" android:layout_height="@dimen/ds_40dp" android:background="@color/white" android:orientation="horizontal" tools:ignore="UseCompoundDrawables"> <TextView android:layout_width="0dp" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginLeft="@dimen/ds_16dp" android:layout_weight="1" android:text="历史搜索" android:textColor="@color/cs_4a4a4a" android:textSize="@dimen/ds_13sp" tools:ignore="HardcodedText,PxUsage,RtlHardcoded" /> <ImageButton android:id="@+id/btn_clean" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginRight="@dimen/ds_16dp" android:background="@drawable/icon_xiaoxi_sousuo_shanchu" tools:ignore="ContentDescription,RtlHardcoded" /> </LinearLayout> <com.yc.stscf.widget.TagFlowLayout android:id="@+id/flow_history" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="@dimen/ds_18dp" android:layout_marginLeft="@dimen/ds_13dp" android:layout_marginRight="@dimen/ds_13dp" /> </LinearLayout> <!--2有数据服务器--> <com.handmark.pulltorefresh.library.PullToRefreshScrollView android:id="@+id/pr_scroll_view" android:layout_width="match_parent" android:layout_height="match_parent" android:divider="@null" android:background="@color/white" android:scrollbars="none" app:ptrAnimationStyle="flip" app:ptrMode="both"> <android.support.v7.widget.RecyclerView android:id="@+id/rv_myRecycler" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@color/white" android:nestedScrollingEnabled="false" /> </com.handmark.pulltorefresh.library.PullToRefreshScrollView> </LinearLayout>
样式解析,部分图片资源什么的,大家自己替换下即将
布局中用到的自定义控件:(PullToRefreshScrollView就不提供了,这块大家注释掉吧)
自定义的edit,可以让光标一直显示在后面。
package com.yc.stscf.widget; import android.annotation.SuppressLint; import android.content.Context; import android.util.AttributeSet; import android.widget.EditText; /** * ================================================ * * @author :vip * @version :V 1.0.0 * @date :2019/6/6 14:56 * 描 述:光标在后的editView * 修订历史: * ================================================ */ @SuppressLint("AppCompatCustomView") public class LastInputEditText extends EditText { public LastInputEditText(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } public LastInputEditText(Context context, AttributeSet attrs) { super(context, attrs); } public LastInputEditText(Context context) { super(context); } @Override protected void onSelectionChanged(int selStart, int selEnd) { super.onSelectionChanged(selStart, selEnd); //保证光标始终在最后面,//防止不能多选 if (selStart == selEnd) { setSelection(getText().length()); } } }
流式布局三部曲之一,全粘贴即可
package com.yc.stscf.widget; import android.content.Context; import android.content.res.TypedArray; import android.os.Bundle; import android.os.Parcelable; import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; import android.view.View; import android.view.ViewGroup; import com.yc.stscf.R; import com.yc.stscf.adapter.TagAdapter; import java.util.HashSet; import java.util.Iterator; import java.util.Set; /** * ================================================ * * @author:vip 版 本:V 1.0.0 * 创建日期:2018/12/05 * 描 述:搜索自定义布局 * 修订历史: * ================================================ */ public class TagFlowLayout extends FlowLayout implements TagAdapter.OnDataChangedListener { private TagAdapter mTagAdapter; /** * -1为不限制数量 **/ private int mSelectedMax = -1; private static final String TAG = "TagFlowLayout"; private Set<Integer> mSelectedView = new HashSet<Integer>(); private OnSelectListener mOnSelectListener; private OnTagClickListener mOnTagClickListener; public interface OnSelectListener { void onSelected(Set<Integer> selectPosSet); } public interface OnTagClickListener { boolean onTagClick(View view, int position, FlowLayout parent); } public TagFlowLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.TagFlowLayout); mSelectedMax = ta.getInt(R.styleable.TagFlowLayout_max_select, -1); ta.recycle(); } 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 void setOnSelectListener(OnSelectListener onSelectListener) { mOnSelectListener = onSelectListener; } public void setOnTagClickListener(OnTagClickListener onTagClickListener) { mOnTagClickListener = onTagClickListener; } 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()); 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); } ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); tagView.setLayoutParams(lp); tagViewContainer.addView(tagView); addView(tagViewContainer); if (preCheckedList.contains(i)) { setChildChecked(i, tagViewContainer); } if (mTagAdapter.setSelected(i, adapter.getItem(i))) { setChildChecked(i, tagViewContainer); } tagView.setClickable(false); final TagView finalTagViewContainer = tagViewContainer; final int position = i; tagViewContainer.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { doSelect(finalTagViewContainer, position); if (mOnTagClickListener != null) { mOnTagClickListener.onTagClick(finalTagViewContainer, position, TagFlowLayout.this); } } }); } mSelectedView.addAll(preCheckedList); } 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 setChildChecked(int position, TagView view) { view.setChecked(true); mTagAdapter.onSelected(position, view.getTagView()); } private void setChildUnChecked(int position, TagView view) { view.setChecked(false); mTagAdapter.unSelected(position, view.getTagView()); } private void doSelect(TagView child, int position) { 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); setChildUnChecked(preIndex, pre); setChildChecked(position, child); mSelectedView.remove(preIndex); mSelectedView.add(position); } else { if (mSelectedMax > 0 && mSelectedView.size() >= mSelectedMax) { return; } setChildChecked(position, child); mSelectedView.add(position); } } else { setChildUnChecked(position, child); 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()); StringBuilder selectPos = new StringBuilder(); if (mSelectedView.size() > 0) { for (int key : mSelectedView) { selectPos.append(key).append("|"); } selectPos = new StringBuilder(selectPos.substring(0, selectPos.length() - 1)); } bundle.putString(KEY_CHOOSE_POS, selectPos.toString()); 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) { setChildChecked(index, tagView); } } } super.onRestoreInstanceState(bundle.getParcelable(KEY_DEFAULT)); return; } super.onRestoreInstanceState(state); } @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); } }
流式布局三部曲之二,全粘贴即可
package com.yc.stscf.widget; import android.annotation.SuppressLint; import android.content.Context; import android.content.res.TypedArray; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; import com.yc.stscf.R; import java.util.ArrayList; import java.util.List; /** * ================================================ * * @author :Vip * @version :V 1.0.0 * @date :2019/7/8 14:12 * 描 述:搜索自定义控件 * 修订历史: * ================================================ */ 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<>(); protected List<Integer> mLineHeight = new ArrayList<>(); protected List<Integer> mLineWidth = new ArrayList<>(); private int mGravity; private List<View> lineViews = new ArrayList<>(); public FlowLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); @SuppressLint("CustomViewStyleable") TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.TagFlowLayout); mGravity = ta.getInt(R.styleable.TagFlowLayout_tag_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); 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<>(); } 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; default: 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); } }
流式布局三部曲之三,全粘贴即可
package com.yc.stscf.adapter; import android.util.Log; import android.view.View; import com.yc.stscf.widget.FlowLayout; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; /** * ================================================ * * @author:vip 版 本:V 1.0.0 * 创建日期:2018/12/19 * 描 述:搜索及评价列表使用适配器(流式布局) * 注: * 修订历史: * ================================================ */ public abstract class TagAdapter<T> { private List<T> mTagDatas; public OnDataChangedListener mOnDataChangedListener; @Deprecated private HashSet<Integer> mCheckedPosList = new HashSet<Integer>(); public TagAdapter(List<T> datas) { mTagDatas = datas; } @Deprecated public TagAdapter(T[] datas) { mTagDatas = new ArrayList<T>(Arrays.asList(datas)); } public interface OnDataChangedListener { void onChanged(); } public void setOnDataChangedListener(OnDataChangedListener listener) { mOnDataChangedListener = listener; } /** * 清除选中的tab **/ @Deprecated public void setSelectedList(int... poses) { Set<Integer> set = new HashSet<>(); for (int pos : poses) { set.add(pos); } setSelectedList(set); } @Deprecated public void setSelectedList(Set<Integer> set) { mCheckedPosList.clear(); if (set != null) { mCheckedPosList.addAll(set); } notifyDataChanged(); } @Deprecated public HashSet<Integer> getPreCheckedList() { return mCheckedPosList; } public int getCount() { return mTagDatas == null ? 0 : mTagDatas.size(); } public void notifyDataChanged() { if (mOnDataChangedListener != null) { mOnDataChangedListener.onChanged(); } } public T getItem(int position) { return mTagDatas.get(position); } public abstract View getView(FlowLayout parent, int position, T t); public void onSelected(int position, View view) { Log.d("zhy", "onSelected " + position); } public void unSelected(int position, View view) { Log.d("zhy", "unSelected " + position); } public boolean setSelected(int position, T t) { return false; } }
样式引用
<!--搜索--> <declare-styleable name="TagFlowLayout"> <attr name="max_select" format="integer" /> <attr name="tag_gravity"> <enum name="left" value="-1" /> <enum name="center" value="0" /> <enum name="right" value="1" /> </attr> </declare-styleable>
二 主界面逻辑
package com.yc.stscf.activity; import android.annotation.SuppressLint; import android.app.Activity; import android.content.Context; import android.content.SharedPreferences; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.text.Editable; import android.text.InputFilter; import android.text.Spanned; import android.text.TextUtils; import android.text.TextWatcher; import android.util.Log; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; import android.widget.ImageButton; import android.widget.LinearLayout; import android.widget.RelativeLayout; import android.widget.TextView; import android.widget.Toast; import com.handmark.pulltorefresh.library.PullToRefreshScrollView; import com.yc.stscf.R; import com.yc.stscf.adapter.MessageReminderAdapter; import com.yc.stscf.adapter.TagAdapter; import com.yc.stscf.base.BaseActivity; import com.yc.stscf.model.MessageReminderBean; import com.yc.stscf.storage.PreferenceCache; import com.yc.stscf.utils.Tt; import com.yc.stscf.widget.FlowLayout; import com.yc.stscf.widget.LastInputEditText; import com.yc.stscf.widget.TagFlowLayout; import java.util.ArrayList; import java.util.List; import butterknife.BindView; /** * ================================================ * * @author :Vip * @version :V 1.0.0 * @date :2019/7/8 13:40 * 描 述:搜索界面 * 修订历史: * ================================================ */ public class SearchActivity extends BaseActivity implements View.OnClickListener { TagAdapter tagAdapter; @BindView(R.id.btn_gray_search) ImageButton btnGraySearch; @BindView(R.id.ce_search) LastInputEditText ceSearch; @BindView(R.id.btn_gray_delete) ImageButton btnGrayDelete; @BindView(R.id.rl_search) RelativeLayout rlSearch; @BindView(R.id.tv_search_cancel) TextView tvSearchCancel; @BindView(R.id.rl_yellow_search) RelativeLayout rlYellowSearch; @BindView(R.id.rl_no_date) RelativeLayout rlNoDate; @BindView(R.id.rv_myRecycler) RecyclerView rvMyRecycler; @BindView(R.id.pr_scroll_view) PullToRefreshScrollView prScrollView; @BindView(R.id.btn_clean) ImageButton btnClean; @BindView(R.id.flow_history) TagFlowLayout flowHistory; List<MessageReminderBean.DataBean> dataBeanList = new ArrayList<>(); MessageReminderAdapter messageReminderAdapter; @BindView(R.id.ll_history) LinearLayout llHistory; private List<String> vip = new ArrayList<>(); /** * 缓存为用户登录id **/ public String KEY_SEARCH_HISTORY_KEYWORD = PreferenceCache.getUsername(); /** * 使用SharedPreferences记录搜索历史 **/ private SharedPreferences mPref; @Override public void initRootView() { setContentView(R.layout.activity_search); } @Override public void initView() { //换行变搜索 ceSearch.setImeOptions(EditorInfo.IME_ACTION_SEARCH); ceSearch.setInputType(EditorInfo.TYPE_CLASS_TEXT); //限制名称只能输入中文和字母 editLimit(); //输入内容监听刷新 listenerEdit(); } /** * 输入文本内容类型限制 **/ private void editLimit() { ceSearch.setFilters(new InputFilter[]{ new InputFilter() { @Override public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) { for (int i = start; i < end; i++) { if (!Character.isLetterOrDigit(source.charAt(i)) && !"_".equals(Character.toString(source.charAt(i))) && !"-".equals(Character.toString(source.charAt(i)))) { return ""; } } return null; } } }); } @Override public void initData() { mPref = getSharedPreferences("input", Activity.MODE_PRIVATE); //初始化历搜 initHistory(); //服务器内容 initRecycler(); } /** * 初始化历史搜索 **/ private void initHistory() { String history = mPref.getString(KEY_SEARCH_HISTORY_KEYWORD, ""); if (!TextUtils.isEmpty(history)) { List<String> list = new ArrayList<String>(); for (Object o : history.split(",")) { list.add((String) o); } vip = list; historySetData(list); } } /** * 服务器内容 **/ private void initRecycler() { if (rvMyRecycler.getRecycledViewPool() != null) { rvMyRecycler.getRecycledViewPool().setMaxRecycledViews(0, 10); } rvMyRecycler.setLayoutManager(new LinearLayoutManager(mContext, LinearLayoutManager.VERTICAL, false)); messageReminderAdapter = new MessageReminderAdapter(R.layout.item_message_reminder, dataBeanList); rvMyRecycler.setAdapter(messageReminderAdapter); /**通知类型,读取状态,内容,日期**/ MessageReminderBean.DataBean messageReminderBean = new MessageReminderBean.DataBean(); messageReminderBean.setTzType("0"); messageReminderBean.setContent("您的授信申请SGJK-SX-SX-201905-0001已审批完成。"); messageReminderBean.setDqStatus("0"); messageReminderBean.setDate("2019-10-01"); MessageReminderBean.DataBean messageReminderBean1 = new MessageReminderBean.DataBean(); messageReminderBean1.setTzType("1"); messageReminderBean1.setContent("您的授信申请SGJK-SX-SX-201905-0001已审批完成。"); messageReminderBean1.setDqStatus("1"); messageReminderBean1.setDate("2019-10-01"); MessageReminderBean.DataBean messageReminderBean2 = new MessageReminderBean.DataBean(); messageReminderBean2.setTzType("2"); messageReminderBean2.setContent("您的授信申请SGJK-SX-SX-201905-0001已审批完成。"); messageReminderBean2.setDqStatus("0"); messageReminderBean2.setDate("2019-10-01"); MessageReminderBean.DataBean messageReminderBean3 = new MessageReminderBean.DataBean(); messageReminderBean3.setTzType("0"); messageReminderBean3.setContent("您的授信申请SGJK-SX-SX-201905-0001已审批完成。"); messageReminderBean3.setDqStatus("0"); messageReminderBean3.setDate("2019-10-01"); dataBeanList.add(messageReminderBean); dataBeanList.add(messageReminderBean1); dataBeanList.add(messageReminderBean2); dataBeanList.add(messageReminderBean3); messageReminderAdapter.notifyDataSetChanged(); } @SuppressLint("ClickableViewAccessibility") @Override public void initListener() { btnClean.setOnClickListener(this); btnGrayDelete.setOnClickListener(this); tvSearchCancel.setOnClickListener(this); //搜索框的触摸事件(不写无法唤起焦点) ceSearch.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { //点击输入框显示搜索并获取焦点 ceSearch.setFocusable(true); ceSearch.setFocusableInTouchMode(true); return false; } }); //换行改为搜索按钮 ceSearch.setOnEditorActionListener(new TextView.OnEditorActionListener() { @Override public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { if (actionId == EditorInfo.IME_ACTION_SEND || (event != null && event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) { clickSearchAfter(); return true; } return false; } }); } /** * 搜索输入按钮监听显隐删除按钮 **/ private void listenerEdit() { final TextWatcher textWatcher = new 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) { } @Override public void afterTextChanged(Editable s) { if (ceSearch.getText().toString().trim().length() > 0) { btnGrayDelete.setVisibility(View.VISIBLE); } else { btnGrayDelete.setVisibility(View.GONE); } } }; ceSearch.setOnFocusChangeListener(new View.OnFocusChangeListener() { @Override public void onFocusChange(View v, boolean hasFocus) { if (hasFocus) { ceSearch.addTextChangedListener(textWatcher); } else { ceSearch.removeTextChangedListener(textWatcher); } } }); } /** * 点击搜索按钮之后的共通逻辑 * 注:丢光标不需要处理了,搜索后直接跳页面,键盘则会自动隐藏 **/ @SuppressLint("ApplySharedPref") private void clickSearchAfter() { if (ceSearch.getText().toString().trim().length() > 0) { //TODO 临时为了展示 if ("小猫".equals(ceSearch.getText().toString().trim())) { rlNoDate.setVisibility(View.VISIBLE); } else { rlNoDate.setVisibility(View.GONE); } String text = ceSearch.getText().toString().trim(); String oldText = mPref.getString(KEY_SEARCH_HISTORY_KEYWORD, ""); List<String>shaList=Arrays.asList(oldText.split(",")); Log.e("tag", "" + oldText); Log.e("Tag", "" + text); Log.e("Tag", "" + oldText.contains(text)); if (!TextUtils.isEmpty(text) && !(shaList.contains(text))) { //Todo 限制位数 // if (vip != null) { // if (vip.size() > 5) { // //最多保存条数 // return; // } // } SharedPreferences.Editor editor = mPref.edit(); editor.putString(KEY_SEARCH_HISTORY_KEYWORD, text + "," + oldText); editor.commit(); vip.add(0, text); } else { Tt.showToast(mContext, "历史搜索已存在您想要输入的内容~!"); } historySetData(vip); ceSearch.setFocusable(false); ceSearch.setFocusableInTouchMode(false); ceSearch.clearFocus(); String searchContent = ceSearch.getText().toString().trim(); Toast.makeText(mContext, "搜索内容为:" + searchContent, Toast.LENGTH_SHORT).show(); //TODO 点了搜索以后,而且查到东西了,先显示列表, llHistory.setVisibility(View.GONE); prScrollView.setVisibility(View.VISIBLE); } else { prScrollView.setVisibility(View.GONE); llHistory.setVisibility(View.VISIBLE); Tt.showShort(mContext, "搜索内容不能为空"); } hintKeyBoard(); } /** * 清空数据 */ @SuppressLint("ApplySharedPref") private void deleteData() { mPref = getSharedPreferences("input", MODE_PRIVATE); SharedPreferences.Editor editor = mPref.edit(); editor.remove(KEY_SEARCH_HISTORY_KEYWORD).commit(); vip.clear(); historySetData(vip); Toast.makeText(mContext, "清楚搜索历史成功", Toast.LENGTH_LONG).show(); } /** * 历史记录填充数据 **/ private void historySetData(final List<String> history) { flowHistory.setAdapter(tagAdapter = new TagAdapter<String>(history) { @Override public View getView(FlowLayout parent, int position, String s) { TextView tv = new TextView(mContext); tv.setTextSize(12); tv.setPadding(40, 18, 40, 18); tv.setTextColor(getResources().getColor(R.color.cs_4a4a4a)); tv.setBackgroundResource(R.drawable.bg_gray_round_small); tv.setText(s); return tv; } }); flowHistory.setOnTagClickListener(new TagFlowLayout.OnTagClickListener() { @Override public boolean onTagClick(View view, int position, FlowLayout parent) { Toast.makeText(mContext, history.get(position), Toast.LENGTH_SHORT).show(); return true; } }); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.btn_clean: //清空缓存 deleteData(); break; case R.id.btn_gray_delete: ceSearch.setText(""); prScrollView.setVisibility(View.GONE); llHistory.setVisibility(View.VISIBLE); rlNoDate.setVisibility(View.GONE); break; case R.id.tv_search_cancel: finish(); break; default: break; } } /** * 关闭键盘 **/ public void hintKeyBoard() { View view = getWindow().peekDecorView(); if (view != null) { InputMethodManager inputManger = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); assert inputManger != null; inputManger.hideSoftInputFromWindow(view.getWindowToken(), 0); } } }
补充:BaseActivity 是我自己封装的界面,大家可以替换成原始的Activity,没任何影响。
三精髓剖析:
自己先建立一个缓存,存用户id,作为唯一标识,登录时候就存。我没给提供PreferenceCache这个类,大家可以先按正规操作去处理,或者临时写个:“abc”.
防止输入的edit输入了表情符号等诡异内容,因为在正规搜索的时候,我们第一是要存本地缓存,第二是将输入内容传到服务器做关联查询,防止服务器这边java后端未做处理导致的异常,我们在移动端提前预判一下。
一进到页面,查这个缓存里,有没有东西,有的话,就循环找处理,添加到最开始我们放的全局的vip这个list里,命名有点随意了,见谅。
这两个方法按注释描述执行即可,需要加,一个是增加了搜索的焦点,能让你操作edit很省心,二是唤起键盘时候,把键盘的换行按钮改写为搜索,因为有的需求没有搜索按钮。
删除按钮 的显隐,没什么大用这玩意。也是为了满足需求写的。
搜索内容不为空的情况下,图中的“小猫”是为了显示无数据时候的占位图,只是为了测试逻辑,这里你可以按自己实际需求替换,这可以理解为:当你输入了内容,点击了搜索,把输入内容传入服务,调用服务器返回了成功情况下,无查询到数据,那么显示占位图。
下方的String text 是输入的内容,oldText是从本地缓存拿到的历史内容,如果输入的内容在历史记录里有,不存并提示,否则存进去。,存这个拼接的。这个是指,你心里有没有我的方法。
清空。很简洁了。清空完了刷新数据,给吐司提示。
填充数据,常规写法。就这么去动态设置文本的间距样式吧。
总结:
没什么总结的了,肯定好用。切换用户账号是好用的,博主采用了3个手机号登录,输入了不同的搜索内容,切换账号登录均能显示对应的历史记录,清空操作也好用,清除的是单独的用户的搜索记录。有问题欢迎留言,代码写的粗糙了点,只为实现功能,等项目有了服务器数据,再二次封装优化。
重点:
图中 老的历史数据要转换成List之后再去比较,否则在执行 contains方法时候会被包含。