安卓实现本地数据库搜索历史记录功能

2 篇文章 0 订阅
1 篇文章 0 订阅

项目场景:

最近项目上有个新功能需要实现一个本地数据的搜索历史记录,在这记录一下,主要使用到的功能,以及使用的一些技术和代码。

例如:项目场景:首先第一次进入搜索页面需要展示(如果有搜索历史展示搜索历史(限制最多三行,超出的自行删除)没有的话展示空页面)。有一个清除的按钮。数据是每次搜索的时候,存在本地即可。需要注意的是,如果这个有多人使用的情况下。需要添加唯一标识来区分每个人的数据。


功能实现

首先就是在实现这个功能前需要先继承一个三方库,这边是从网上下载了一个大神的demo:
第一步就是先导入依赖implementation 'com.hyman:flowlayout-lib:1.1.2'会下载一个流式布局的三方库。
第一步

public class RecordsDao {
    RecordSQLiteOpenHelper recordHelper;

    SQLiteDatabase recordsDb;

    public RecordsDao(Context context) {
        recordHelper = new RecordSQLiteOpenHelper(context);
    }

    //添加搜索记录
    public void addRecords(String record ,String code) {

        if (!isHasRecord(record)) {
            recordsDb = recordHelper.getReadableDatabase();
            ContentValues values = new ContentValues();
            values.put("name", record);
            values.put("code",code);
            //添加
            recordsDb.insert("records", null, values);
            //关闭
            recordsDb.close();
        }
    }

    //判断是否含有该搜索记录
    public boolean isHasRecord(String record) {
        boolean isHasRecord = false;
        recordsDb = recordHelper.getReadableDatabase();
        Cursor cursor = recordsDb.query("records", null, null, null, null, null, null);
        while (cursor.moveToNext()) {
            if (record.equals(cursor.getString(cursor.getColumnIndexOrThrow("name")))) {
                isHasRecord = true;
            }
        }
        //关闭数据库
        recordsDb.close();
        cursor.close();
        return isHasRecord;
    }

    //获取全部搜索记录
    public List<String> getRecordsList() {
        List<String> recordsList = new ArrayList<>();
        recordsDb = recordHelper.getReadableDatabase();
        Cursor cursor = recordsDb.query("records", null, null, null, null, null, null);
        while (cursor.moveToNext()) {
            String name = cursor.getString(cursor.getColumnIndexOrThrow("name"));
            recordsList.add(name);
        }
        //关闭数据库
        recordsDb.close();
        cursor.close();
        return recordsList;
    }

    //模糊查询
    public List<Map<String, Object>> querySimlarRecord(String record, String code) {
        StringBuffer queryStr = new StringBuffer("select * from records ");

        if (code != null) {
            queryStr.append(" where code='" + code + "'");
        }
        if (!StringUtils.isEmpty(record) && queryStr.toString().contains("where")) {
            queryStr.append(" and name like '%" + record + "%' order by name ");
        }else if (!StringUtils.isEmpty(record) && !queryStr.toString().contains("where")) {
            queryStr.append(" where name like '%" + record + "%' order by name ");
        }
        List<Map<String, Object>> similarRecords = new ArrayList<>();
        Cursor cursor = recordHelper.getReadableDatabase().rawQuery(queryStr.toString(), null);

        while (cursor.moveToNext()) {
            Map<String, Object> map = new HashMap<>(2);
            String name = cursor.getString(cursor.getColumnIndexOrThrow("name"));
            Integer id = cursor.getInt(cursor.getColumnIndexOrThrow("_id"));
            map.put("name", name);
            map.put("id", id);
            similarRecords.add(map);
        }

        cursor.close();
        return similarRecords;
    }

    //清空搜索记录
    public void deleteAllRecords() {
        recordsDb = recordHelper.getWritableDatabase();
        recordsDb.execSQL("delete from records");

        recordsDb.close();
    }

    //删除单个搜索记录
    public void deleteRecords(Integer id) {
        recordsDb = recordHelper.getWritableDatabase();
        if (!Objects.isNull(id)) {
            recordsDb.execSQL("delete from records WHERE _id =" + id);
        }
        recordsDb.close();

    }

}
public class RecordSQLiteOpenHelper extends SQLiteOpenHelper {

    private final static String DB_NAME = "temp.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, name,code TEXT);";
        db.execSQL(sqlStr);
    }

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

    }
}

布局:
在这里插入图片描述
initview里面初始化的方法:
在这里插入图片描述code是唯一标识,区分用户的。
第二步

//颠倒list顺序,用户输入的信息会从上依次往下显示
    private void reversedList() {
        searchRecordsList.clear();
        String size = PreferencesUtil.getString(this, PreferencesEntivity.SEARCHHISTORYSIZE, "");//页面上展示总的item个数
        String Allsize = PreferencesUtil.getString(this, PreferencesEntivity.SEARCHHISTORYALLSIZE, "");//显示的行数
        if (!TextUtils.isEmpty(size) && !TextUtils.isEmpty(Allsize)) {
            int sizes = (Integer.parseInt(size));
            int Allsizes = (Integer.parseInt(Allsize));
            if (!TextUtils.isEmpty(size) && !TextUtils.isEmpty(Allsize) && tempList.size() > sizes && Allsizes + 1 > 3) {//最大行数为3,加1为了删除多余的数据
                int i1 = (tempList.size()) - sizes;
                for (int i = 0; i < i1; i++) {
                    Integer id = (Integer) tempList.get(i).get("id");
                    recordsDao.deleteRecords(id);
                    PreferencesUtil.putString(this, PreferencesEntivity.SEARCHHISTORYSIZE, 0 + "");
                }
                for (int i = tempList.size() - 1; i >= 0; i--) {//删除完多余的数据,倒序将数据插入到新的集合里面
                    String name = (String) tempList.get(i).get("name");
                    Integer id = (Integer) tempList.get(i).get("id");
                    HashMap<String, Object> map = new HashMap<>();
                    map.put("name", name);
                    map.put("id", id);
                    searchRecordsList.add(map);
                }
            } else {
                for (int i = tempList.size() - 1; i >= 0; i--) {
                    String name = (String) tempList.get(i).get("name");
                    Integer id = (Integer) tempList.get(i).get("id");
                    HashMap<String, Object> map = new HashMap<>();
                    map.put("name", name);
                    map.put("id", id);
                    searchRecordsList.add(map);
                }
            }
        } else {
            for (int i = tempList.size() - 1; i >= 0; i--) {
                String name = (String) tempList.get(i).get("name");
                Integer id = (Integer) tempList.get(i).get("id");
                HashMap<String, Object> map = new HashMap<>();
                map.put("name", name);
                map.put("id", id);
                searchRecordsList.add(map);
            }
        }

    }

实现倒序,自动删除限制三行以外的数据。

//当没有匹配的搜索数据的时候不显示历史记录栏
    private void checkRecordsSize() {
        if (tempList.size() == 0) {
            Co_history.setVisibility(View.GONE);//搜索的历史数据
            ns_list.setVisibility(View.GONE);
        } else {
            ns_list.setVisibility(View.GONE);
            Co_history.setVisibility(View.VISIBLE);
            setAdapter(rv_history);
        }
    }

    //设置适配器
    private void setAdapter(MaxLineFlowLayout mFlowLayout) {
        //限制最大行数
        mFlowLayout.setMaxLine(3, recordsDao);
        //设置数据
        mFlowLayout.setAdapter(new TagAdapter<Map<String, Object>>(searchRecordsList) {
            @Override
            public View getView(FlowLayout parent, int position, Map<String, Object> stringObjectMap) {
                //加入你的布局
                TextView tv = (TextView) View.inflate(SearchAllActivity.this, R.layout.search_records, null);
                String s = (String) stringObjectMap.get("name");
                tv.setText(s);
                return tv;
            }
        });

        //点击历史记录,直接搜索
        mFlowLayout.setOnTagClickListener(new TagFlowLayout.OnTagClickListener() {
            @Override
            public boolean onTagClick(View view, int position, FlowLayout parent) {
                String Click_History = (String) searchRecordsList.get(position).get("name");
                Log.i("搜索历史", "onTagClick: " + Click_History);
                et_search.setText(Click_History);
                return false;
            }
        });
    }

因为这个三方库里面少一个限制最大行数的功能。我重新继承了一下,修改了一下他的绘制方法。

/**
 * 流式布局  https://github.com/hongyangAndroid/FlowLayout
 * <p>
 * Change by mChenys on 2019/1/8.
 * 修改后,支持行数限制,优化了在onLayout的时候的二次循环初始化参数,支持获取当前可见的item个数
 */
public class MaxLineFlowLayout extends TagFlowLayout implements TagAdapter.OnDataChangedListener {
    private static final String TAG = "FlowLayout";
    protected static final int LEFT = -1;
    protected static final int CENTER = 0;
    protected 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>();//记录所有行宽
    protected List<View> lineViews = new ArrayList<>();//临时记录每行的view
    protected int mGravity;
    private int maxLine = -1;//最大行数
    private RecordsDao recordsDao;
    private boolean isExceedingMaxLimit; //预设的子View是否超出了最大行数限制

    public void setMaxLine(int maxLine, RecordsDao recordsDao) {
        this.maxLine = maxLine;
        this.recordsDao = recordsDao;
    }

    public MaxLineFlowLayout(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);
        int layoutDirection = TextUtilsCompat.getLayoutDirectionFromLocale(Locale.getDefault());
        if (layoutDirection == LayoutDirection.RTL) {
            if (mGravity == LEFT) {
                mGravity = RIGHT;
            } else {
                mGravity = LEFT;
            }
        }
        ta.recycle();
    }

    public MaxLineFlowLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MaxLineFlowLayout(Context context) {
        this(context, null);
    }


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        mAllViews.clear();//记录所有行的view
        mLineHeight.clear();//记录每一行的高度
        mLineWidth.clear();//记录每一行的宽度
        lineViews.clear();//记录每一行的view

        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();
//        LogUtils.e("cys", "cCount:" + cCount);
        for (int i = 0; i < cCount; i++) {
            View child = getChildAt(i);

            if (child.getVisibility() == View.GONE) {
                continue;
            }
            //测量子view
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
            MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
            //子View宽高
            int childWidth = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
            int childHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;

            if (lineWidth + childWidth > sizeWidth - getPaddingLeft() - getPaddingRight()) {
                if (maxLine > 0 && mAllViews.size() + 1 >= maxLine) { //+1是因为后面还有最后一行
                    isExceedingMaxLimit = true;
                    break;//超过最大行数跳出循环
                }
                //需要换行
                if (i == 0 && width == 0 && lineWidth == 0 && height == 0 && lineHeight == 0) {
                    //如果第一个子View就满足换行条件,那么width和height就是子View的宽高
                    width = lineWidth = childWidth;
                    height = lineHeight = childHeight;
                    lineViews.add(child);
                } else {
                    width = Math.max(width, lineWidth);//记录最大行宽
                    height += lineHeight;//累加包裹内容所需的高度
                }

                //换行前,保存当前行数据
                mLineHeight.add(lineHeight);
                mLineWidth.add(lineWidth);
                mAllViews.add(lineViews);

                //换行,新行数据初始化
                lineWidth = 0;//重新赋值行宽
                lineHeight = 0;//重新赋值行高
                lineViews = new ArrayList<View>();//创建新行

                if (i == 0 && width > 0 && height > 0) {
                    //如果第一个子View就满足换行条件并且数据已经保存,则不需要下面重复添加了
                    continue;
                }

            }
            //新行或者当前行继续添加子View
            lineWidth += childWidth;//累加行宽
            lineHeight = Math.max(lineHeight, childHeight);//取当前行最大高度作为行高
            lineViews.add(child);
        }

        //添加最后一行数据
        width = Math.max(lineWidth, width);//包裹内容所需的最大宽度
        height += lineHeight;//累加高度
        mLineHeight.add(lineHeight);
        mLineWidth.add(lineWidth);
        mAllViews.add(lineViews);
        setMeasuredDimension(
                //父控件宽高确定则用确定的,否则用测量后的
                modeWidth == MeasureSpec.EXACTLY ? sizeWidth : width + getPaddingLeft() + getPaddingRight(),
                modeHeight == MeasureSpec.EXACTLY ? sizeHeight : height + getPaddingTop() + getPaddingBottom()//
        );
        int totalLine = getTotalLine();
        int totalByLine = getTotalByLine(totalLine);
        PreferencesUtil.putString(getContext(), PreferencesEntivity.SEARCHHISTORYSIZE, totalByLine + "");
        PreferencesUtil.putString(getContext(), PreferencesEntivity.SEARCHHISTORYALLSIZE, totalLine + "");
        Log.i(TAG, "changeAdapter: " + totalLine + totalByLine);
    }


    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
//        LogUtils.e("cys","onLayout");
        //总宽
        int width = getWidth();
        //当前已用行宽高
        int lineHeight = 0;
        //下面是对每一行的View进行布局
        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里面的数组倒序排,从右边开始存放view
                    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);

                //更新下一个view添加到当前行的left
                left += child.getMeasuredWidth() + lp.leftMargin
                        + lp.rightMargin;
            }
            //更新下一个view添加到下一行的top
            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);
    }

    /**
     * 获取指定行数内的item个数
     *
     * @return 每行的个数之和
     * @lineNum 总行数
     */
    public int getTotalByLine(int lineNum) {
        int count = 0;
        if (lineNum <= mAllViews.size()) {
            for (int i = 0; i < lineNum; i++) {
                List<View> line = mAllViews.get(i);
                count += line.size();
            }
        } else {
            for (int i = 0; i < mAllViews.size(); i++) {
                List<View> line = mAllViews.get(i);
                count += line.size();
            }
        }

        return count;
    }

    /**
     * 返回总行数
     *
     * @return
     */
    public int getTotalLine() {
        return mAllViews.size();
    }

    /**
     * 设置的数据是否超过了最大限制
     *
     * @return
     */
    public boolean isExceedingMaxLimit() {
        return isExceedingMaxLimit;
    }


    private TagAdapter mTagAdapter;
    private int mSelectedMax = -1;//-1为不限制数量

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

    private TagFlowLayout.OnSelectListener mOnSelectListener;
    private TagFlowLayout.OnTagClickListener mOnTagClickListener;


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

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

    public void setOnSelectListener(TagFlowLayout.OnSelectListener onSelectListener) {
        mOnSelectListener = onSelectListener;
    }


    public void setOnTagClickListener(TagFlowLayout.OnTagClickListener onTagClickListener) {
        mOnTagClickListener = onTagClickListener;
    }

    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 < mTagAdapter.getCount(); i++) {
            Log.i("删除搜索历史", "changeAdapter: " + mTagAdapter.getCount());
            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,
                                MaxLineFlowLayout.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));
        }
    }


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

}

使用到sp保存了两个字段用来删除数据用的。


最后附上效果图

在这里插入图片描述

  • 11
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值