Android自定义流式布局实现淘宝搜索记录

本文实例为大家分享了Android实现淘宝搜索记录的具体代码,供大家参考,具体内容如下

效果如下:

废话不多说

实现代码:

attrs.xml

<declare-styleable name="TagFlowLayout">
 <!--最大选择数量-->
 <attr name="max_select" format="integer"/>
 <!--最大可显示行数-->
 <attr name="limit_line_count" format="integer"/>
 <!--是否设置多行隐藏-->
 <attr name="is_limit" format="boolean"/>
 <attr name="tag_gravity">
  <enum name="left" value="-1"/>
  <enum name="center" value="0"/>
  <enum name="right" value="1"/>
 </attr>
</declare-styleable>

TagFlowLayout .java

public class TagFlowLayout extends FlowLayout
 implements TagAdapter.OnDataChangedListener {

 private static final String TAG = "TagFlowLayout";
 private TagAdapter mTagAdapter;
 private int mSelectedMax = -1;//-1为不限制数量

 private Set<Integer> mSelectedView = new HashSet<Integer>();

 private OnSelectListener mOnSelectListener;
 private OnTagClickListener mOnTagClickListener;
 private OnLongClickListener mOnLongClickListener;

 public interface OnSelectListener {
 void onSelected(Set<Integer> selectPosSet);
 }

 public interface OnTagClickListener {
 void onTagClick(View view, int position, FlowLayout parent);
 }

 public interface OnLongClickListener {
 void onLongClick(View view, int position);
 }

 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 setOnLongClickListener(OnLongClickListener onLongClickListener) {
 mOnLongClickListener = onLongClickListener;
 }

 public void setAdapter(TagAdapter adapter) {
 mTagAdapter = adapter;
 mTagAdapter.setOnDataChangedListener(this);
 mSelectedView.clear();
 changeAdapter();
 }

 @SuppressWarnings("ResourceType")
 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 {
  MarginLayoutParams lp = new MarginLayoutParams(
   LayoutParams.WRAP_CONTENT,
   LayoutParams.WRAP_CONTENT);
  lp.setMargins(dip2px(getContext(), 5),
   dip2px(getContext(), 5),
   dip2px(getContext(), 5),
   dip2px(getContext(), 5));
  tagViewContainer.setLayoutParams(lp);
  }
  LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, 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);
   }
  }
  });

  tagViewContainer.setOnLongClickListener(new View.OnLongClickListener() {
  @Override
  public boolean onLongClick(View v) {
   if (mOnLongClickListener != null) {
   mOnLongClickListener.onLongClick(finalTagViewContainer, position);
   //消费事件,不让事件继续下去
   return true;
   }
   return false;
  }
  });
 }
 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());

 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) {
   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);
 }
}

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);
 }

}

FlowLayout

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;

 private int limitLineCount; //默认显示3行 断词条显示3行,长词条显示2行
 private boolean isLimit; //是否有行限制
 private boolean isOverFlow; //是否溢出2行

 private int mGravity;
 protected List<List<View>> mAllViews = new ArrayList<List<View>>();
 protected List<Integer> mLineHeight = new ArrayList<Integer>();
 protected List<Integer> mLineWidth = new ArrayList<Integer>();
 private List<View> lineViews = new ArrayList<>();

 public boolean isOverFlow() {
 return isOverFlow;
 }

 private void setOverFlow(boolean overFlow) {
 isOverFlow = overFlow;
 }

 public boolean isLimit() {
 return isLimit;
 }

 public void setLimit(boolean limit) {
 if (!limit) {
  setOverFlow(false);
 }
 isLimit = limit;
 }

 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_tag_gravity, LEFT);
 limitLineCount = ta.getInt(R.styleable.TagFlowLayout_limit_line_count, 3);
 isLimit = ta.getBoolean(R.styleable.TagFlowLayout_is_limit, false);
 int layoutDirection = TextUtilsCompat.getLayoutDirectionFromLocale(Locale.getDefault());
 if (layoutDirection == LayoutDirection.RTL) {
  if (mGravity == LEFT) {
  mGravity = RIGHT;
  } else {
  mGravity = 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 lineCount = 0;//记录当前的行数

 int cCount = getChildCount();

 for (int i = 0; i < cCount; i++) {
  View child = getChildAt(i);
  if (child.getVisibility() == View.GONE) {
  if (i == cCount - 1) {
   if (isLimit) {
   if (lineCount == limitLineCount) {
    setOverFlow(true);
    break;
   } else {
    setOverFlow(false);
   }
   }

   width = Math.max(lineWidth, width);
   height += lineHeight;
   lineCount++;
  }
  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()) {
  if (isLimit) {
   if (lineCount == limitLineCount) {
   setOverFlow(true);
   break;
   } else {
   setOverFlow(false);
   }
  }
  width = Math.max(width, lineWidth);
  lineWidth = childWidth;
  height += lineHeight;
  lineHeight = childHeight;
  lineCount++;
  } else {
  lineWidth += childWidth;
  lineHeight = Math.max(lineHeight, childHeight);
  }
  if (i == cCount - 1) {
  if (isLimit) {
   if (lineCount == limitLineCount) {
   setOverFlow(true);
   break;
   } else {
   setOverFlow(false);
   }
  }
  width = Math.max(lineWidth, width);
  height += lineHeight;
  lineCount++;
  }
 }
 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 lineCount = 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()) {
  if (isLimit) {
   if (lineCount == limitLineCount) {
   break;
   }
  }

  mLineHeight.add(lineHeight);
  mAllViews.add(lineViews);
  mLineWidth.add(lineWidth);

  lineWidth = 0;
  lineHeight = childHeight + lp.topMargin + lp.bottomMargin;
  lineViews = new ArrayList<View>();
  lineCount++;
  }
  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:
   // 适配了rtl,需要补偿一个padding值
   left = width - (currentLineWidth + getPaddingLeft()) - getPaddingRight();
   // 适配了rtl,需要把lineViews里面的数组倒序排
   Collections.reverse(lineViews);
   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);
 }
}

TagAdapter

public abstract class TagAdapter<T> {
 private List<T> mTagDatas;
 private OnDataChangedListener mOnDataChangedListener;
 @Deprecated
 private HashSet<Integer> mCheckedPosList = new HashSet<Integer>();

 public TagAdapter(List<T> datas) {
 mTagDatas = datas;
 }

 public void setData(List<T> datas) {
 mTagDatas = datas;
 }

 @Deprecated
 public TagAdapter(T[] datas) {
 mTagDatas = new ArrayList<T>(Arrays.asList(datas));
 }

 interface OnDataChangedListener {
 void onChanged();
 }

 void setOnDataChangedListener(OnDataChangedListener listener) {
 mOnDataChangedListener = listener;
 }

 @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
 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;
 }
}

RecordsDao

public class RecordsDao {
 private final String TABLE_NAME = "records";
 private SQLiteDatabase recordsDb;
 private RecordSQLiteOpenHelper recordHelper;
 private NotifyDataChanged mNotifyDataChanged;
 private String mUsername;

 public RecordsDao(Context context, String username) {
 recordHelper = new RecordSQLiteOpenHelper(context);
 mUsername = username;
 }

 public interface NotifyDataChanged {
 void notifyDataChanged();
 }

 /**
 * 设置数据变化监听
 */
 public void setNotifyDataChanged(NotifyDataChanged notifyDataChanged) {
 mNotifyDataChanged = notifyDataChanged;
 }

 /**
 * 移除数据变化监听
 */
 public void removeNotifyDataChanged() {
 if (mNotifyDataChanged != null) {
  mNotifyDataChanged = null;
 }
 }

 private synchronized SQLiteDatabase getWritableDatabase() {
 return recordHelper.getWritableDatabase();
 }

 private synchronized SQLiteDatabase getReadableDatabase() {
 return recordHelper.getReadableDatabase();
 }

 /**
 * 如果考虑操作频繁可以到最后不用数据库时关闭
 * <p>
 * 关闭数据库
 */
 public void closeDatabase() {
 if (recordsDb != null) {
  recordsDb.close();
 }
 }

 /**
 * 添加搜索记录
 *
 * @param record 记录
 */
 public void addRecords(String record) {
 //如果这条记录没有则添加,有则更新时间
 int recordId = getRecordId(record);
 try {
  recordsDb = getReadableDatabase();
  if (-1 == recordId) {
  ContentValues values = new ContentValues();
  values.put("username", mUsername);
  values.put("keyword", record);
  //添加搜索记录
  recordsDb.insert(TABLE_NAME, null, values);
  } else {
  Date d = new Date();
  @SuppressLint("SimpleDateFormat") SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  //更新搜索历史数据时间
  ContentValues values = new ContentValues();
  values.put("time", sdf.format(d));
  recordsDb.update(TABLE_NAME, values, "_id = ?", new String[]{Integer.toString(recordId)});
  }
  if (mNotifyDataChanged != null) {
  mNotifyDataChanged.notifyDataChanged();
  }
 } catch (Exception e) {
  e.printStackTrace();
 }
 }

 /**
 * 判断是否含有该搜索记录
 *
 * @param record 记录
 * @return true | false
 */
 public boolean isHasRecord(String record) {
 boolean isHasRecord = false;
 Cursor cursor = null;
 try {
  recordsDb = getReadableDatabase();
  cursor = recordsDb.query(TABLE_NAME, null, "username = ?", new String[]{mUsername}, null, null, null);
  while (cursor.moveToNext()) {
  if (record.equals(cursor.getString(cursor.getColumnIndexOrThrow("keyword")))) {
   isHasRecord = true;
  }
  }
 } catch (IllegalArgumentException e) {
  e.printStackTrace();
 } finally {
  if (cursor != null) {
  //关闭游标
  cursor.close();
  }
 }
 return isHasRecord;
 }

 /**
 * 判断是否含有该搜索记录
 *
 * @param record 记录
 * @return id
 */
 public int getRecordId(String record) {
 int isHasRecord = -1;
 Cursor cursor = null;
 try {
  recordsDb = getReadableDatabase();
  cursor = recordsDb.query(TABLE_NAME, null, "username = ?", new String[]{mUsername}, null, null, null);
  while (cursor.moveToNext()) {
  if (record.equals(cursor.getString(cursor.getColumnIndexOrThrow("keyword")))) {
   isHasRecord = cursor.getInt(cursor.getColumnIndexOrThrow("_id"));
  }
  }
 } catch (IllegalArgumentException e) {
  e.printStackTrace();
 } finally {
  if (cursor != null) {
  //关闭游标
  cursor.close();
  }
 }
 return isHasRecord;
 }

 /**
 * 获取当前用户全部搜索记录
 *
 * @return 记录集合
 */
 public List<String> getRecordsList() {
 List<String> recordsList = new ArrayList<>();
 Cursor cursor = null;
 try {
  recordsDb = getReadableDatabase();
  cursor = recordsDb.query(TABLE_NAME, null, "username = ?", new String[]{mUsername}, null, null, "time desc");
  while (cursor.moveToNext()) {
  String name = cursor.getString(cursor.getColumnIndexOrThrow("keyword"));
  recordsList.add(name);
  }
 } catch (IllegalArgumentException e) {
  e.printStackTrace();
 } finally {
  if (cursor != null) {
  //关闭游标
  cursor.close();
  }
 }
 return recordsList;
 }

 /**
 * 获取指定数量搜索记录
 *
 * @return 记录集合
 */
 public List<String> getRecordsByNumber(int recordNumber) {
 List<String> recordsList = new ArrayList<>();
 if (recordNumber < 0) {
  throw new IllegalArgumentException();
 } else if (0 == recordNumber) {
  return recordsList;
 } else {
  Cursor cursor = null;
  try {
  recordsDb = getReadableDatabase();
  cursor = recordsDb.query(TABLE_NAME, null, "username = ?", new String[]{mUsername}, null, null, "time desc limit " + recordNumber);
  while (cursor.moveToNext()) {
   String name = cursor.getString(cursor.getColumnIndexOrThrow("keyword"));
   recordsList.add(name);
  }
  } catch (IllegalArgumentException e) {
  e.printStackTrace();
  } finally {
  if (cursor != null) {
   //关闭游标
   cursor.close();
  }
  }
 }
 return recordsList;
 }

 /**
 * 模糊查询
 *
 * @param record 记录
 * @return 返回类似记录
 */
 public List<String> querySimlarRecord(String record) {
 List<String> similarRecords = new ArrayList<>();
 Cursor cursor = null;
 try {
  recordsDb = getReadableDatabase();
  cursor = recordsDb.query(TABLE_NAME, null, "username = ? and keyword like '%?%'", new String[]{mUsername, record}, null, null, "order by time desc");
  while (cursor.moveToNext()) {
  String name = cursor.getString(cursor.getColumnIndexOrThrow("keyword"));
  similarRecords.add(name);
  }
 } catch (IllegalArgumentException e) {
  e.printStackTrace();
 } finally {
  if (cursor != null) {
  //关闭游标
  cursor.close();
  }
 }
 return similarRecords;
 }

 /**
 * 清除指定用户的搜索记录
 */
 public void deleteUsernameAllRecords() {
 try {
  recordsDb = getWritableDatabase();
  recordsDb.delete(TABLE_NAME, "username = ?", new String[]{mUsername});
  if (mNotifyDataChanged != null) {
  mNotifyDataChanged.notifyDataChanged();
  }
 } catch (SQLException e) {
  e.printStackTrace();
  Log.e(TABLE_NAME, "清除所有历史记录失败");
 } finally {
 }
 }

 /**
 * 清空数据库所有的历史记录
 */
 public void deleteAllRecords() {
 try {
  recordsDb = getWritableDatabase();
  recordsDb.execSQL("delete from " + TABLE_NAME);
  if (mNotifyDataChanged != null) {
  mNotifyDataChanged.notifyDataChanged();
  }
 } catch (SQLException e) {
  e.printStackTrace();
  Log.e(TABLE_NAME, "清除所有历史记录失败");
 } finally {
 }
 }

 /**
 * 通过id删除记录
 *
 * @param id 记录id
 * @return 返回删除id
 */
 public int deleteRecord(int id) {
 int d = -1;
 try {
  recordsDb = getWritableDatabase();
  d = recordsDb.delete(TABLE_NAME, "_id = ?", new String[]{Integer.toString(id)});
  if (mNotifyDataChanged != null) {
  mNotifyDataChanged.notifyDataChanged();
  }
 } catch (Exception e) {
  e.printStackTrace();
  Log.e(TABLE_NAME, "删除_id:" + id + "历史记录失败");
 }
 return d;
 }

 /**
 * 通过记录删除记录
 *
 * @param record 记录
 */
 public int deleteRecord(String record) {
 int recordId = -1;
 try {
  recordsDb = getWritableDatabase();
  recordId = recordsDb.delete(TABLE_NAME, "username = ? and keyword = ?", new String[]{mUsername, record});
  if (mNotifyDataChanged != null) {
  mNotifyDataChanged.notifyDataChanged();
  }
 } catch (SQLException e) {
  e.printStackTrace();
  Log.e(TABLE_NAME, "清除所有历史记录失败");
 }
 return recordId;
 }
}

RecordSQLiteOpenHelper

public class RecordSQLiteOpenHelper extends SQLiteOpenHelper {
 private final static String DB_NAME = "search_history.db";
 private final static int DB_VERSION = 1;

 public RecordSQLiteOpenHelper(Context context) {
 super(context, DB_NAME, null, DB_VERSION);
 }

 @Override
 public void onCreate(SQLiteDatabase db) {
 String sqlStr = "CREATE TABLE IF NOT EXISTS records (_id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT, keyword TEXT, time NOT NULL DEFAULT (datetime('now','localtime')));";
 db.execSQL(sqlStr);
 }

 @Override
 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

 }
}

item_background.xml

<shape xmlns:android="http://schemas.android.com/apk/res/android">

 <solid android:color="#F8F8F8">
 </solid>

 <corners android:radius="40dp"/>

 <padding
 android:bottom="4dp"
 android:left="12dp"
 android:right="12dp"
 android:top="4dp"/>
</shape>

search_item_background.xml

<shape xmlns:android="http://schemas.android.com/apk/res/android">

 <solid android:color="#F8F8F8">
 </solid>

 <corners android:radius="40dp"/>

 <padding
 android:bottom="2dp"
 android:left="10dp"
 android:right="10dp"
 android:top="2dp"/>
</shape>

tv_history.xml

<TextView xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:layout_margin="5dp"
  android:background="@drawable/item_background"
  android:text="搜索历史"
  android:singleLine="true"
  android:textColor="#666666"
  android:textSize="13sp">
</TextView>

activity_main.xml

<android.support.constraint.ConstraintLayout
 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="@android:color/white"
 tools:context="com.demo.www.MainActivity">

 <!--标题栏-->
 <android.support.constraint.ConstraintLayout
 android:id="@+id/cl_toolbar"
 android:layout_width="match_parent"
 android:layout_height="?attr/actionBarSize"
 android:background="@color/colorAccent"
 android:orientation="horizontal"
 app:layout_constraintTop_toTopOf="parent">

 <ImageView
  android:id="@+id/iv_back"
  android:layout_width="wrap_content"
  android:layout_height="match_parent"
  android:paddingLeft="@dimen/space_large"
  android:paddingRight="@dimen/space_large"
  android:src="@mipmap/home"/>

 <LinearLayout
  android:layout_width="0dp"
  android:layout_height="match_parent"
  android:layout_marginBottom="10dp"
  android:layout_marginTop="10dp"
  android:background="@drawable/search_item_background"
  android:focusable="true"
  android:focusableInTouchMode="true"
  android:gravity="center"
  android:orientation="horizontal"
  android:paddingLeft="12dp"
  android:paddingRight="12dp"
  app:layout_constraintLeft_toRightOf="@+id/iv_back"
  app:layout_constraintRight_toLeftOf="@+id/iv_search">

  <EditText
  android:id="@+id/edit_query"
  android:layout_width="0dp"
  android:layout_height="match_parent"
  android:layout_weight="1"
  android:background="@null"
  android:hint="请输入搜索关键字"
  android:imeOptions="actionSearch"
  android:singleLine="true"
  android:textSize="14sp"/>

  <ImageView
  android:id="@+id/iv_clear_search"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:layout_marginBottom="@dimen/space_normal"
  android:layout_marginTop="@dimen/space_normal"
  android:src="@mipmap/ic_delete"/>
 </LinearLayout>

 <TextView
  android:id="@+id/iv_search"
  android:layout_width="wrap_content"
  android:layout_height="match_parent"
  android:gravity="center"
  android:paddingLeft="@dimen/space_large"
  android:paddingRight="@dimen/space_large"
  android:text="搜索"
  android:textColor="@android:color/white"
  app:layout_constraintRight_toRightOf="parent"/>
 </android.support.constraint.ConstraintLayout>

 <!--历史搜索-->
 <LinearLayout
 android:id="@+id/ll_history_content"
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:orientation="vertical"
 android:paddingLeft="@dimen/space_large"
 android:paddingRight="@dimen/space_large"
 android:paddingTop="@dimen/space_normal"
 app:layout_constraintTop_toBottomOf="@+id/cl_toolbar">

 <android.support.constraint.ConstraintLayout
  android:layout_width="match_parent"
  android:layout_height="wrap_content">

  <TextView
  android:id="@+id/tv_history_hint"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:text="搜索历史"
  android:textColor="#383838"
  android:textSize="14sp"/>

  <ImageView
  android:id="@+id/clear_all_records"
  android:layout_width="wrap_content"
  android:layout_height="match_parent"
  android:src="@mipmap/ic_delete_history"
  app:layout_constraintBottom_toTopOf="parent"
  app:layout_constraintRight_toRightOf="parent"
  app:layout_constraintTop_toBottomOf="parent"/>
 </android.support.constraint.ConstraintLayout>

 <TagFlowLayout
  android:id="@+id/fl_search_records"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:paddingTop="@dimen/space_normal"
  app:is_limit="true"
  app:limit_line_count="3"
  app:max_select="1">
 </TagFlowLayout>

 <ImageView
  android:id="@+id/iv_arrow"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:src="@mipmap/ic_arrow"
  android:visibility="gone"/>
 </LinearLayout>
</android.support.constraint.ConstraintLayout>

MainActivity.java

public class MainActivity extends AppCompatActivity {

 private RecordsDao mRecordsDao;
 //默然展示词条个数
 private final int DEFAULT_RECORD_NUMBER = 10;
 private List<String> recordList = new ArrayList<>();
 private TagAdapter mRecordsAdapter;
 private LinearLayout mHistoryContent;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 //默认账号
 String username = "007";
 //初始化数据库
 mRecordsDao = new RecordsDao(this, username);

 final EditText editText = findViewById(R.id.edit_query);
 final TagFlowLayout tagFlowLayout = findViewById(R.id.fl_search_records);
 final ImageView clearAllRecords = findViewById(R.id.clear_all_records);
 final ImageView moreArrow = findViewById(R.id.iv_arrow);
 TextView search = findViewById(R.id.iv_search);
 ImageView clearSearch = findViewById(R.id.iv_clear_search);
 mHistoryContent = findViewById(R.id.ll_history_content);

 initData();

 //创建历史标签适配器
 //为标签设置对应的内容
 mRecordsAdapter = new TagAdapter<String>(recordList) {

  @Override
  public View getView(FlowLayout parent, int position, String s) {
  TextView tv = (TextView) LayoutInflater.from(MainActivity.this).inflate(R.layout.tv_history,
   tagFlowLayout, false);
  //为标签设置对应的内容
  tv.setText(s);
  return tv;
  }
 };

 tagFlowLayout.setAdapter(mRecordsAdapter);
 tagFlowLayout.setOnTagClickListener(new TagFlowLayout.OnTagClickListener() {
  @Override
  public void onTagClick(View view, int position, FlowLayout parent) {
  //清空editText之前的数据
  editText.setText("");
  //将获取到的字符串传到搜索结果界面,点击后搜索对应条目内容
  editText.setText(recordList.get(position));
  editText.setSelection(editText.length());
  }
 });
 //删除某个条目
 tagFlowLayout.setOnLongClickListener(new TagFlowLayout.OnLongClickListener() {
  @Override
  public void onLongClick(View view, final int position) {
  showDialog("确定要删除该条历史记录?", new DialogInterface.OnClickListener() {
   @Override
   public void onClick(DialogInterface dialog, int which) {
   //删除某一条记录
   mRecordsDao.deleteRecord(recordList.get(position));
   }
  });
  }
 });

 //view加载完成时回调
 tagFlowLayout.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
  @Override
  public void onGlobalLayout() {
  boolean isOverFlow = tagFlowLayout.isOverFlow();
  boolean isLimit = tagFlowLayout.isLimit();
  if (isLimit && isOverFlow) {
   moreArrow.setVisibility(View.VISIBLE);
  } else {
   moreArrow.setVisibility(View.GONE);
  }
  }
 });

 moreArrow.setOnClickListener(new View.OnClickListener() {
  @Override
  public void onClick(View v) {
  tagFlowLayout.setLimit(false);
  mRecordsAdapter.notifyDataChanged();
  }
 });

 //清除所有记录
 clearAllRecords.setOnClickListener(new View.OnClickListener() {
  @Override
  public void onClick(View v) {
  showDialog("确定要删除全部历史记录?", new DialogInterface.OnClickListener() {
   @Override
   public void onClick(DialogInterface dialog, int which) {
   tagFlowLayout.setLimit(true);
   //清除所有数据
   mRecordsDao.deleteUsernameAllRecords();
   }
  });
  }
 });

 search.setOnClickListener(new View.OnClickListener() {
  @Override
  public void onClick(View v) {
  String record = editText.getText().toString();
  if (!TextUtils.isEmpty(record)) {
   //添加数据
   mRecordsDao.addRecords(record);
  }
  }
 });

 clearSearch.setOnClickListener(new View.OnClickListener() {
  @Override
  public void onClick(View v) {
  //清除搜索历史
  editText.setText("");
  }
 });

 mRecordsDao.setNotifyDataChanged(new RecordsDao.NotifyDataChanged() {
  @Override
  public void notifyDataChanged() {
  initData();
  }
 });
 }

 private void showDialog(String dialogTitle, @NonNull DialogInterface.OnClickListener onClickListener) {
 AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
 builder.setMessage(dialogTitle);
 builder.setPositiveButton("确定", onClickListener);
 builder.setNegativeButton("取消", null);
 builder.create().show();
 }

 private void initData() {
 Observable.create(new ObservableOnSubscribe<List<String>>() {
  @Override
  public void subscribe(ObservableEmitter<List<String>> emitter) throws Exception {
  emitter.onNext(mRecordsDao.getRecordsByNumber(DEFAULT_RECORD_NUMBER));
  }
 }).subscribeOn(Schedulers.io())
  .observeOn(AndroidSchedulers.mainThread())
  .subscribe(new Consumer<List<String>>() {
   @Override
   public void accept(List<String> s) throws Exception {
   recordList.clear();
   recordList = s;
   if (null == recordList || recordList.size() == 0) {
    mHistoryContent.setVisibility(View.GONE);
   } else {
    mHistoryContent.setVisibility(View.VISIBLE);
   }
   if (mRecordsAdapter != null) {
    mRecordsAdapter.setData(recordList);
    mRecordsAdapter.notifyDataChanged();
   }
   }
  });
 }

 @Override
 protected void onDestroy() {
 mRecordsDao.closeDatabase();
 mRecordsDao.removeNotifyDataChanged();
 super.onDestroy();
 }
}

欢迎大家点赞,评论!

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Android实现搜索保存历史记录功能

    本文实例为大家分享了Android搜索保存历史记录功能,供大家参考,具体内容如下 要点:就是缓存输入的内容到 本地 下面就是实现保存 搜索内容到本地 和 清空本地历史的方法 //保存搜索内容到本地 public void save() { String text = mKeywordEt.getText().toString(); String oldText = mSharePreference.getString(SEARCH_HISTORY, ""); StringBuilder

  • Android本地实现搜索历史记录

    本文实例为大家分享了Android本地实现搜索历史记录的具体代码,供大家参考,具体内容如下 一.自定义搜索历史记录 本地实现搜索历史记录有很多种方法,下面不多说了,我们来用SQLite来实现此功能,直接上完整代码:点击下载源码 效果一: 效果二: 1.MainActivity主函数 package com.example.administrator.searchapplication; import android.support.v7.app.AppCompatActivity; import

  • Android流式布局实现历史搜索记录功能

    最近在开发项目的时候,有一个需求是展示历史搜索记录 ,展示的样式是流式布局(就是根据内容自动换行).在网上看到了一个不错的类库跟大家分享一下 首先在AndroidStudio简历一个工程项目导入module类库,我会把项目demo方法GitHub上 说一下demo中的实现方式 在 activity_main.xml中 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android

  • Android项目类似淘宝 电商 搜索功能,监听软键盘搜索事件,延迟自动搜索,以及时间排序的搜索历史记录的实现

    最近跳槽去新公司,接受的第一个任务是在 一个电商模块的搜索功能以及搜索历史记录的实现. 需求和淘宝等电商的功能大体差不多,最上面一个搜索框,下面显示搜索历史记录.在EditText里输入要搜索的关键字后,按软键盘的搜索按键/延迟xxxxms后自动搜索.然后将搜索的内容展示给用户/提示用户没有搜到相关信息. 历史记录是按时间排序的,最新的在前面,输入以前搜索过的关键字,例如牛仔裤(本来是第二条),会更新这条记录的时间,下次再看,牛仔裤的排列就在第一位了.并且有清除历史记录的功能. 整理需求,大致需

  • Android实现搜索功能并本地保存搜索历史记录

    本文实例为大家分享了Android实现搜索功能,并且需要显示搜索的历史记录,供大家参考,具体内容如下 效果图: 本案例实现起来很简单,所以可以直接拿来嵌入项目中使用,涉及到的知识点: - 数据库的增删改查操作 - ListView和ScrollView的嵌套冲突解决 - 监听软键盘回车按钮设置为搜索按钮 - 使用TextWatcher( )实时筛选 - 已搜索的关键字再次搜索不重复添加到数据库 - 刚进入页面设置软键盘不因为EditText而自动弹出 代码 RecordSQLiteOpenHel

  • Android自定义流式布局实现淘宝搜索记录

    本文实例为大家分享了Android实现淘宝搜索记录的具体代码,供大家参考,具体内容如下 效果如下: 废话不多说 实现代码: attrs.xml <declare-styleable name="TagFlowLayout"> <!--最大选择数量--> <attr name="max_select" format="integer"/> <!--最大可显示行数--> <attr name=&q

  • Android自定义流式布局/自动换行布局实例

    最近,Google开源了一个流式排版库"FlexboxLayout",功能强大,支持多种排版方式,如各种方向的自动换行等,具体资料各位可搜索学习^_^. 由于我的项目中,只需要从左到右S型的自动换行,需求效果图如下: 使用FlexboxLayout这个框架未免显得有些臃肿,所以自己动手写了一个流式ViewGroup. 安卓中自定义ViewGroup的步骤是: 1. 新建一个类,继承ViewGroup 2. 重写构造方法 3. 重写onMeasure.onLayout方法 onMeasu

  • Android自定义流式布局的实现示例

    在日常的app使用中,我们会在android 的app中看见 热门标签等自动换行的流式布局,今天,我们就来看看如何自定义一个类似热门标签那样的流式布局.下面我们就来详细介绍流式布局的应用特点以及用的的技术点. 1.流式布局的特点以及应用场景 特点:当上面一行的空间不够容纳新的TextView时候,才开辟下一行的空间. 原理图: 场景:主要用于关键词搜索或者热门标签等场景 2.自定义ViewGroup (1)onMeasure:测量子view的宽高,设置自己的宽和高 (2)onLayout:设置子

  • Android简单实现自定义流式布局的方法

    本文实例讲述了Android简单实现自定义流式布局的方法.分享给大家供大家参考,具体如下: 首先来看一下 手淘HD - 商品详情 - 选择商品属性 页面的UI 商品有很多尺码,而且展现每个尺码所需要的View的大小也不同(主要是宽度),所以在从服务器端拉到数据之前,展现所有尺码所需要的行数和每一行的个数都无法确定,因此不能直接使用GridView或ListView. 如果使用LinearLayout呢? 一个LinearLayout只能显示一行,如果要展示多行,则每一行都要new一个Linear

  • Android FlowLayout流式布局实现详解

    本文实例为大家分享了Android FlowLayout流式布局的具体代码,供大家参考,具体内容如下 最近使用APP的时候经常看到有 这种流式布局 ,今天我就跟大家一起来动手撸一个这种自定义控件. 首先说一下自定义控件的流程: 自定义控件一般要么继承View要么继承ViewGroup View的自定义流程: 继承一个View-->重写onMeasure方法-->重写onDraw方法-->定义自定义属性-->处理手势操作 ViewGroup的自定义流程: 继承一个ViewGroup-

  • Android 深入探究自定义view之流式布局FlowLayout的使用

    引子 文章开始前思考个问题,view到底是如何摆放到屏幕上的?在xml布局中,我们可能用到match_parent.wrap_content或是具体的值,那我们如何转为具体的dp?对于层层嵌套的布局,他们用的都不是具体的dp,我们又该如何确定它们的尺寸? 下图是实现效果 自定义View的流程 想想自定义view我们都要做哪些事情 布局,我们要确定view的尺寸以及要摆放的位置,也就是 onMeasure() .onLayout() 两方法 显示,布局之后是怎么把它显示出来,主要用的是onDraw

  • Android实现简单的自定义ViewGroup流式布局

    目录 前言 一.基本的测量与布局 二.流式的布局的layout 三.流式的布局的Measure 后记 前言 前面几篇我们简单的复习了一下自定义 View 的测量与绘制,并且回顾了常见的一些事件的处理方式. 那么如果我们想自定义 ViewGroup 的话,它和自定义View又有什么区别呢?其实我们把 ViewGroup 当做 View 来用的话也不是不可以.但是既然我们用到了容器 ViewGroup 当时是想用它的一些特殊的特性了. 比如 ViewGroup 的测量,ViewGroup的布局,Vi

  • Android流式布局FlowLayout详解

    现在商城类的APP几乎都要用到流式布局来实现选择属性功能,在我的demo中是通过FlowLayout工具类实现流式布局 使用起来非常简单,十几行代码就可以实现: 在我们的项目中大部分都是单选效果,为了防止用到多选,demo中也实现了多选: FlowLayout大家不用研究怎么实现的,只要会使用就好: 就好比谷歌提供的ListView条目点击事件一样,只要会用就好,没必要研究个所以然:大家在用的时候直接从demo中复制到项目中即可: 大家可以将FlowLayout理解为一个线性布局:将准备好的一个

  • Android 简单实现一个流式布局的示例

    本篇文章主要介绍了Android 简单实现一个流式布局的示例,分享给大家,具体如下: 流式布局应该是我们很常见的一种布局了,在很多场景下都会遇到它,例如:标签之类的功能等.用轮子不如造轮子来的爽,这里自己简单的实现下流式布局: onMeasure onLayout 通过以上两个方法我们就可以完成对流式布局的基本操作: onMeasure @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)

  • Android自定View流式布局根据文字数量换行

    本文实例为大家分享了Android根据文字数量换行的具体代码,供大家参考,具体内容如下 //主页 定义数据框 package com.example.customwaterfallviewgroup; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.EditText; import java.util

随机推荐