1. 程式人生 > >Android 本地歷史記錄、及產品標籤(支援單選、多選)實現(附原始碼)(使用鴻洋大神的FlowLayout開源庫)

Android 本地歷史記錄、及產品標籤(支援單選、多選)實現(附原始碼)(使用鴻洋大神的FlowLayout開源庫)

        最近的專案需要做本地歷史記錄功能,以前寫的有些過時了,就在網上查了查較好的開源庫、實現方式等。最終選擇了鴻洋大神的FlowLayout流式佈局,再搭配SharedPreferencesUtil工具類來實現本地歷史記錄功能。同時FlowLayout開源庫還非常適合實現產品標籤(支援單選、多選)功能,所以在Demo中也有具體的使用,研究研究總是沒錯的。

        開源庫的下載、匯入等操作就不再廢話了,直接開始專案演示。

        注:文章末尾附專案原始碼下載地址及FlowLayout開源庫地址

      效果展示

        主要功能:FlowLayout的使用、從SP中讀取歷史記錄、將歷史記錄寫入到SP中、歷史記錄最大數量限制、歷史記錄不可重複、最新查詢的在最前邊、清楚歷史記錄;FlowLayout預先設定選中、設定最大選中數、設定標籤點選和選中監聽、獲取選中的標籤、通過selecter完成標籤選擇的切換等。方便演示加入了測試資料。

          

      本地歷史記錄的實現

        1.頁面佈局:主要是在佈局檔案中使用TagFlowLayout實現流失佈局,並通過自定義屬性(zhy:max_select="0")規定標籤的最大可選數。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:zhy="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#eeeeee"
    android:orientation="vertical">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@mipmap/titlebar_bg">

        <RelativeLayout
            android:id="@+id/rl"
            android:layout_width="match_parent"
            android:layout_height="48dp"
            android:background="@null">

            <TextView
                android:id="@+id/tv_titlebar_center"
                android:layout_width="200dp"
                android:layout_height="match_parent"
                android:layout_centerHorizontal="true"
                android:ellipsize="end"
                android:gravity="center"
                android:maxLength="18"
                android:singleLine="true"
                android:text="商品查詢"
                android:textColor="@color/themeTextColor"
                android:textSize="@dimen/themeTextSize" />

            <ImageView
                android:id="@+id/iv_titlebar_left"
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:layout_alignParentLeft="true"
                android:background="@null"
                android:padding="14dp"
                android:src="@mipmap/titlebar_back" />
        </RelativeLayout>

        <RelativeLayout
            android:id="@+id/rl_search"
            android:layout_width="match_parent"
            android:layout_height="40dp"
            android:layout_below="@id/rl"
            android:layout_marginLeft="14dp"
            android:layout_marginRight="14dp"
            android:layout_marginTop="24dp"
            android:background="@drawable/shape_circle_corner_white">

            <ImageView
                android:id="@+id/iv_search"
                android:layout_width="30dp"
                android:layout_height="30dp"
                android:layout_alignParentRight="true"
                android:layout_centerVertical="true"
                android:layout_marginRight="5dp"
                android:background="@drawable/shape_circle_corner_blue5"
                android:padding="7dp"
                android:src="@mipmap/titlebar_search" />

            <EditText
                android:id="@+id/et_search"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_toLeftOf="@id/iv_search"
                android:background="@null"
                android:hint="請輸入要查詢的條碼"
                android:paddingLeft="15dp"
                android:paddingRight="15dp"
                android:singleLine="true"
                android:textColorHint="#D7DAE6"
                android:textCursorDrawable="@null"
                android:textSize="14sp" />
        </RelativeLayout>

        <LinearLayout
            android:id="@+id/ll_history"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_below="@id/rl_search"
            android:orientation="vertical"
            android:visibility="gone">

            <com.zhy.view.flowlayout.TagFlowLayout
                android:id="@+id/flowlayout_history"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginLeft="8dp"
                android:layout_marginRight="8dp"
                android:layout_marginTop="11dp"
                zhy:max_select="0"></com.zhy.view.flowlayout.TagFlowLayout>

            <TextView
                android:id="@+id/tv_clear"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:paddingBottom="14dp"
                android:paddingLeft="14dp"
                android:paddingRight="14dp"
                android:paddingTop="5dp"
                android:text="清除歷史記錄"
                android:textColor="#60ffffff"
                android:textSize="12sp" />
        </LinearLayout>
    </RelativeLayout>
</LinearLayout>

        2.定義從SP中讀取歷史記錄方法:從SP中獲取儲存的字串,並通過wy標識將字串分割為字串陣列,然後新增到List中,最後將list返回。

/**
     * 從SP中讀取歷史記錄
     */
    private List<String> readHistory() {
        List<String> readHistoryList = new ArrayList<>();
        String search_history = SharedPreferencesUtil.getString(context, "search_history", null);

        //將String轉為List
        if (!TextUtils.isEmpty(search_history)) {
            String[] strs = search_history.split("wy");
            for (int i = 0; i < strs.length; i++) {
                readHistoryList.add(i, strs[i]);
            }
        }

        return readHistoryList;
    }

        3.定義將歷史記錄寫入到SP中方法:先判空,不為空則獲取已有的歷史記錄,再將新的歷史記錄新增list中,最後把list轉換成字串(新增wy標識)存入到SP中。需要注意的是:1.歷史記錄最大數量限制,2.歷史記錄不可重複,3.最新查詢的在最前邊。

/**
     * 將歷史記錄寫入到SP中
     */
    private void writeHistory(String write) {
        if (TextUtils.isEmpty(write)) {
            return;
        }

        String writeHistory = "";
        //獲取歷史記錄
        List<String> readHistoryList = readHistory();

        //如果不重複,則新增為第一個歷史記錄;
        //如果重複,則刪除已有,再新增為第一個歷史記錄;
        for (int i = 0; i < readHistoryList.size(); i++) {
            boolean hasWrite = readHistoryList.get(i).equals(write);
            if (hasWrite) {
                readHistoryList.remove(i);
                break;
            }
        }
        readHistoryList.add(0, write);

        //歷史記錄最多為10個
        if (readHistoryList.size() > 10) {
            readHistoryList = readHistoryList.subList(0, 10);
        }

        //將ArrayList轉為String
        for (int i = 0; i < readHistoryList.size(); i++) {
            writeHistory += readHistoryList.get(i) + "wy";
        }
        SharedPreferencesUtil.putString(context, "search_history", writeHistory);
    }

        4.初始化歷史記錄:先通過本地歷史記錄判斷頁面的顯示與隱藏,在為FlowLayout填充資料、設定點選事件監聽。

/**
     * 初始化歷史記錄
     */
    private void initHistory() {
        final List<String> readHistory = readHistory();
        if (readHistory != null && readHistory.size() > 0) {
            llHistory.setVisibility(View.VISIBLE);
        } else {
            llHistory.setVisibility(View.GONE);
        }

        //為FlowLayout填充資料
        flowlayoutHistory.setAdapter(new TagAdapter(readHistory) {
            @Override
            public View getView(FlowLayout parent, int position, Object o) {

                TextView view = (TextView) View.inflate(context, R.layout.flowlayout_textview, null);
                view.setText(readHistory.get(position));
                return view;
            }
        });

        //為FlowLayout的標籤設定監聽事件
        flowlayoutHistory.setOnTagClickListener(new TagFlowLayout.OnTagClickListener() {
            @Override
            public boolean onTagClick(View view, int position, FlowLayout parent) {
                ToastUtil.makeText(context, readHistory.get(position));
                etSearch.setText(readHistory.get(position));
                etSearch.setSelection(readHistory.get(position).length());
                return true;
            }
        });
    }

        5.點選搜尋按鈕:首先獲取輸入框中的內容,然後新增到本地歷史記錄中,最後再呼叫initHistory()重新整理頁面資料。具體的搜尋邏輯就不寫了,不是重點。

/**
     * 初始化搜尋
     */
    private void initSearch() {
        String inputSearch = etSearch.getText().toString().trim();
        if (TextUtils.isEmpty(inputSearch)) {
            ToastUtil.makeText(context, "請輸入要查詢的條碼");
            return;
        }
        writeHistory(inputSearch);
        initHistory();
    }

        6.清除歷史記錄:將本地歷史記錄置為空字串,再呼叫initHistory()重新整理頁面資料即可,大功告成。

SharedPreferencesUtil.putString(context, "search_history", "");
                initHistory();

      產品標籤(支援單選、多選)的實現

        ##特色(開源庫中copy的)

  • 以setAdapter形式注入資料

  • 直接設定selector為background即可完成標籤選則的切換,類似CheckBox

  • 支援控制選擇的Tag數量,比如:單選、多選

  • 支援setOnTagClickListener,當點選某個Tag回撥

  • 支援setOnSelectListener,當選擇某個Tag後回撥

  • 支援adapter.notifyDataChanged

  • Activity重建(或者旋轉)後,選擇的狀態自動儲存

        1.頁面佈局:主要是兩個TextVIew和兩個FlowLayout,並不複雜就不展示了。

        2.初始化所有標籤的FlowLayout:包括預先設定選中、設定最大選中數、點選和選中事件監聽。當標籤選中時,初始化選中標籤的FlowLayout;當選中的標籤數量大於等於最大選中數時,給使用者友好的提示。

/**
     * 初始化所有標籤的FlowLayout
     */
    private void initFlowLayout() {
        TagAdapter tagAdapter = new TagAdapter(mVals) {
            @Override
            public View getView(FlowLayout parent, int position, Object o) {

                TextView view = (TextView) View.inflate(context, R.layout.flowlayout_textview_selected, null);
                view.setText(mVals[position]);
                return view;
            }
        };
        //預先設定選中
        tagAdapter.setSelectedList(0, 1);
        flowlayout.setAdapter(tagAdapter);

        //設定最大選中數
        flowlayout.setMaxSelectCount(maxSelected);

        //為FlowLayout的標籤設定監聽事件
        flowlayout.setOnTagClickListener(new TagFlowLayout.OnTagClickListener() {
            @Override
            public boolean onTagClick(View view, int position, FlowLayout parent) {
                if (selectedList.size() >= maxSelected) {
                    ToastUtil.makeText(context, "已達最大選中數" + maxSelected);
                } else {
                    ToastUtil.makeText(context, mVals[position]);
                }
                return true;
            }
        });

        //為FlowLayout的標籤設定選中監聽事件
        flowlayout.setOnSelectListener(new TagFlowLayout.OnSelectListener() {
            @Override
            public void onSelected(Set<Integer> selectPosSet) {
                initFlowLayoutSelected();
            }
        });
    }

        3.初始化選中標籤的FlowLayout:獲取所有選中的position的Set集合,通過Iterator遍歷Set集合,並將資料新增到新的String[]中,最後為FlowLayout填充資料。因為在佈局檔案中將此FlowLayout的最大選中數設定為0,所以不能選中了。

/**
     * 初始化選中標籤的FlowLayout
     */
    private void initFlowLayoutSelected() {
        int i = 0;
        //獲得所有選中的position集合,例如[1,2,3,4]
        selectedList = flowlayout.getSelectedList();
        mValsSelected = new String[selectedList.size()];
        Iterator<Integer> iterator = selectedList.iterator();
        while (iterator.hasNext()) {
            mValsSelected[i] = mVals[iterator.next()];
            i++;
        }

        tvSelected.setText("最大選中數為:" + maxSelected + "(已選中" + selectedList.size() + ")" + "(position:" + selectedList.toString() + ")");
        flowlayoutSelected.setAdapter(new TagAdapter(mValsSelected) {
            @Override
            public View getView(FlowLayout parent, int position, Object o) {

                TextView view = (TextView) View.inflate(context, R.layout.flowlayout_textview_no_selected, null);
                view.setText(mValsSelected[position]);
                return view;
            }
        });
    }

      小結

        以上就是兩個功能:本地歷史記錄和產品標籤的實現了,雖然功能不算複雜,但完成之後還是很有成就感的,拿著玩一玩吧。GitHub上還有許許多多優秀的開源庫,結合自己的具體需求,開源庫可以實現各種各樣的效果,小夥伴們趕緊去嘗試吧。

      附: