show code block

2017年7月17日 星期一

Android元件(SearchView、Filter) — SearchView應用、在RecyclerView內創建Filter

 前言:

 最近做到這個功能,在這裡做個紀錄。
以前我使用SearchBar的功能是我給後台值,後台直接給我收尋結果。
但如果使用到的收尋結果必須要前端(App端)實作呢?
這裡就教你如何前端做收尋應用。



重點程式碼:

重點都是圍繞在Filter
可以看一下官方文檔, 讓我們把畫面帶到Protected methods
在待會我們實作RecyclerView的時候繼承 Filterable 會用到getFilter() 方法,好讓我們在activity調用adapter時使用篩選。
還會再Adapter內實作一個自己的Filter,這時候會override複寫下方兩種方法。
performFiltering 執行篩選
publishResults 篩選結果

SearchView的部分,必須implements SearchView.OnQueryTextListener

Public methods

abstract booleanonQueryTextChange(String newText)
Called when the query text is changed by the user.
abstract booleanonQueryTextSubmit(String query)
Called when the user submits the query.

onQueryTextChange 當使用者改變searchview的字體時,call此方法。
onQueryTextSubmit 當使用者按下送出時,call此方法。



實作:

這裡提供 SearchView + ListView 的實作方法。
[AndroidStudio] 使用 SearchView 過濾列表資料 —
http://disp.cc/b/11-9ded

裡面的內容清楚明瞭。
如果有問題可以在這裡問。
我實作了一個demo:https://drive.google.com/open?id=0Byk75IYx-dKXWTUya0hKaWtXX3M

但ListView近期已經被RecyclerView代替了。
在RecyclerView內的adapter並沒有filter的方法,因此我們要在Adapter內實作。

提供一個SearchView + RecyclerView的方法
作者:Lipt0n
連結:http://www.jianshu.com/p/5078c7fec29e
來源:簡書
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。

此處就是在Adapter內實作Filterable並使用的很好範例。
等等我做的東西是實踐在這個上面的東西,就不提供DEMO了。


所有的收尋都拿String當例子,簡單明瞭!

但如果我要客製化呢?變成一個item的搜尋模式該怎麼辦呢?
這邊包含可以搜尋文字。
如果每個item的文字都不一樣,是可以分開收尋的。
只要你的文字內有關鍵字,都會跑在下面的結果內。
如圖:




完整程式碼:

先來看看 今日我們會動到的地方:



 因為會有網路下載圖片的功能,所以先在manifest加入Gilde。
如何加入這篇有提到
http://nikeru8.blogspot.tw/2017/03/third-party-frescopicasso.html
就不再贅述。

然後你還要一張default image,供給如果Gilde還沒下載完成時的預設圖片。
http://lmgtfy.com/?q=default_image 

這邊必須強調一下預設圖片和文字的重要性,如果RecyclerView讀取不到你的圖片或是文字,因為他內在的回收機制,他會去偷取其他item的圖片作為預設圖片。你也知道後果了。文不對題,或是錯位的問題就會絡繹不絕。


activity_main.xml

<?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:orientation="vertical"
    tools:context="com.hello.kaiser.searchbar.MainActivity">

    <android.support.v7.widget.SearchView
        android:id="@+id/search_bar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</LinearLayout>

 

先看activity的佈局文件。
先把xml畫好畫滿。
item.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="horizontal">

    <ImageView
        android:id="@+id/image_view"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:padding="5dp" />

    <TextView
        android:id="@+id/text_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:gravity="center"
        android:textSize="30dp" />
</LinearLayout>
 

再來先把工具寫出來,我們要塞物件的DATA
DATA.java
 
public class Data  {
    private String title;
    private String imageUrl;

//    public Data(String title, String imageUrl) {
//        this.title = title;
//        this.imageUrl = imageUrl;
//        Log.d("ItemData", "CheckPoint imageUrl=" + imageUrl);
//    }

    public String getTitle() {
        return title;
    }

    public String getImageUrl() {
        return imageUrl;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public void setImageUrl(String imageUrl) {
        this.imageUrl = imageUrl;
    }

}

這裡我們要塞圖片和文字。

把我們MainActivity.java 全部補上吧。
public class MainActivity extends AppCompatActivity implements SearchView.OnQueryTextListener {

    
    //元件
    private android.support.v7.widget.SearchView searchView;
    private RecyclerView recyclerView;
    private ArrayList<Data> datalists = new ArrayList<>();
    private String[] urlList = null;
    private String[] titleList = null;
    private DataAdapter dataAdapter;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
        initSet();
    }

    /**
     *  這方法在activity所有畫面都完成之後,才會被call。
     *  之前我做的專案,有很多Crash案例都是因為畫面還沒完成,點擊事件就先放行,點擊後導致Crash。
     */
    @Override
    public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);
        initListener();
    }

    /**
     *  init畫面
     */
    private void initView() {
        searchView = (android.support.v7.widget.SearchView) findViewById(R.id.search_bar);
        recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
    }

    /**
     *  init設定
     */
    private void initSet() {
        //RecyclerView設定
        dataAdapter = new DataAdapter(this);
        LinearLayoutManager llm = new LinearLayoutManager(this);
        llm.setOrientation(LinearLayoutManager.VERTICAL);
        recyclerView.setLayoutManager(llm);
        searchView.setOnQueryTextListener(this);
        searchView.setIconifiedByDefault(false); //是否要點選搜尋圖示後再打開輸入框
        searchView.setFocusable(false);
        searchView.requestFocusFromTouch();      //要點選後才會開啟鍵盤輸入
        searchView.setSubmitButtonEnabled(false);//輸入框後是否要加上送出的按鈕
        searchView.setQueryHint("請輸入android搜尋"); //輸入框沒有值時要顯示的提示文字

        //假資料設定
        urlList = new String[]{
                "http://www.jrtstudio.com/sites/default/files/ico_android.png",
                "https://developer.android.com/_static/images/android/touchicon-180.png",
                "http://cdn.bgr.com/2012/11/android-icon.jpg?quality=98&amp;strip=all",
                "http://www.fastbooting.com/uploads/5/5/9/9/55994511/1474140_orig.png",
                "http://www.pendidikan-diy.go.id/dinas_v4/img/up_down/Android.png",
                "https://www.android.com/static/2016/img/one/a1_andy_1x.png",
                "http://zdnet2.cbsistatic.com/hub/i/2016/04/22/a1ced73f-6628-4930-96f4-d224e1d3a707/8a43709ccee674396403ee99472e38f3/android-security-1.jpg",
                "https://unwire.pro/wp-content/uploads/2017/05/android-kotlin.png",
                "http://www.jrtstudio.com/sites/default/files/ico_android.png",
                "https://developer.android.com/_static/images/android/touchicon-180.png",
                "http://cdn.bgr.com/2012/11/android-icon.jpg?quality=98&amp;strip=all",
                "http://www.fastbooting.com/uploads/5/5/9/9/55994511/1474140_orig.png",
                "http://www.pendidikan-diy.go.id/dinas_v4/img/up_down/Android.png",
                "https://www.android.com/static/2016/img/one/a1_andy_1x.png",
                "http://zdnet2.cbsistatic.com/hub/i/2016/04/22/a1ced73f-6628-4930-96f4-d224e1d3a707/8a43709ccee674396403ee99472e38f3/android-security-1.jpg",
                "https://unwire.pro/wp-content/uploads/2017/05/android-kotlin.png",
        };
        titleList = new String[]{
                "手機:iphone7",
                "手機殼:犀牛盾",
                "手機:htc",
                "電腦:戴爾",
                "手機:samsung",
                "電腦:hp惠普",
                "電腦:惠普",
                "電腦:蘋果電腦apple",
                "手機:sony",
                "手機:samsung s8",
                "手機:asus",
                "電腦:Auser宏碁電腦",
                "手機、電腦:微軟",
                "手機:oppo 蕩漾款",
                "手機:小米",
                "電腦:聯想",
        };

        //兩筆假資料size()一樣才進入迴圈
        if (titleList.length == urlList.length)
            for (int i = 0; i &lt; (urlList.length); i++) {
                Data data = new Data();//製作單筆資料
                data.setTitle(titleList[i]);
                data.setImageUrl(urlList[i]);
                datalists.add(data);//把單筆資料加入陣列
            }

        dataAdapter.setData(datalists);//把陣列塞入adapter
        recyclerView.setAdapter(dataAdapter);//把adapter塞入recyclerview
    }


    /**
     *  點擊事件
     */
    private void initListener() {

    }

    /**
     *  複寫implements SearchView.OnQueryTextListener 的方法
     */
    @Override
    public boolean onQueryTextSubmit(final String query) {
        return false;
    }

    @Override
    public boolean onQueryTextChange(final String newText) {
        //塞選 我們寫在adapter內
        dataAdapter.getFilter().filter(newText);
        return false;
    }
}

重頭戲Adapter
DataAdapter.java
class DataAdapter extends RecyclerView.Adapter<DataAdapter.dataHolder> implements Filterable {

    private final static String TAG = DataAdapter.class.getSimpleName();

    //元件
    private Context context;
    private ArrayList<data> datalists;//是會變動的陣列,用來顯示正個recyclerView的資料
    private ArrayList<data> filterDatas;//固定陣列,用來和filter比對用的。
    private MyFliter myFliter;//讓mainActivity能使用adapter呼叫篩選功能

    //建構子
    public DataAdapter(Context context) {
        this.context = context;
    }

    //implements Filterable的必定覆寫的方法,讓我們在activity內能呼叫他
    @Override
    public Filter getFilter() {
        if (myFliter == null) {
            myFliter = new MyFliter();
        }
        return myFliter;
    }

    class dataHolder extends RecyclerView.ViewHolder {
        private ImageView imageView;
        private TextView textView;

        public dataHolder(View itemView) {
            super(itemView);
            imageView = (ImageView) itemView.findViewById(R.id.image_view);
            textView = (TextView) itemView.findViewById(R.id.text_view);
        }
    }

    //讓我們在activity內能讀取或更新adapter內data的資料
    public void setData(ArrayList<data> datalists) {
        this.datalists = datalists;
        filterDatas = datalists;
        notifyDataSetChanged();
    }

    @Override
    public DataAdapter.dataHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item, parent, false);
        dataHolder dh = new dataHolder(view);
        return dh;
    }

    @Override
    public void onBindViewHolder(DataAdapter.dataHolder holder, int position) {
        Glide.with(context)
                .load(datalists.get(position).getImageUrl())
                .placeholder(R.drawable.default_image)
                .into(holder.imageView);

        if (!TextUtils.isEmpty(datalists.get(position).getTitle()))
            holder.textView.setText(datalists.get(position).getTitle());
        else
            holder.textView.setText("暫無資料");
    }

    @Override
    public int getItemCount() {
        return datalists == null ? 0 : datalists.size();
    }

    //實作自己的篩選Filter
    class MyFliter extends Filter {
        @Override
        protected FilterResults performFiltering(CharSequence filterContent) {
            ArrayList<data> filterData = new ArrayList&lt;&gt;();
            //先判斷filterContent是不是null才進入
            if (filterContent != null &amp;&amp; filterContent.toString().trim().length() &gt; 0) {
                Log.d(TAG, "確認是否為空值。 filterData.size = " + filterData.size());
                //這回圈是在判斷你輸入的文字(filterContent)是否有在filterDatas陣列內有相關的文字,逐條搜尋。
                for (int i = 0; i &lt; filterDatas.size(); i++) {
                    String content = filterDatas.get(i).getTitle();
                    Log.d(TAG, "確認是否進入for迴圈 content = " + content);
                    if (content.contains(String.valueOf(filterContent))) {
                        Log.d(TAG, "確認輸入文字是否相同。");
                        Data data = new Data();
                        data.setImageUrl(filterDatas.get(i).getImageUrl());
                        data.setTitle(filterDatas.get(i).getTitle());
                        filterData.add(data);
                    }
                }
            } else {
                filterData = filterDatas;
                Log.d(TAG, "確認什麼都沒打 filterDatas = datalists = " + filterData.size());

            }
            FilterResults filterResults = new FilterResults();
            filterResults.count = filterData.size();
            filterResults.values = filterData;
            Log.d(TAG, "final size = " + filterResults.count);
            return filterResults;
        }

        @Override
        protected void publishResults(CharSequence charSequence, FilterResults filterResults) {
            datalists = (ArrayList<data>) filterResults.values;
            if (filterResults.count &gt; 0) {
                notifyDataSetChanged();
            } else {
                Data data = new Data();
                data.setTitle("沒有結果");
                datalists.add(data);
                notifyDataSetChanged();
            }
        }
    }
}
MyFliter 這方法內強破複寫兩種方法
performFiltering 執行篩選
publishResults 篩選結果
performFiltering
1、我先判斷在SearchView內的有無輸入任何文字(是否為null)
2、在寫一個for迴圈,把所有進入adatper陣列資料內的title String做比對,如果有相同的文字,就創建一個data物件,塞入你要篩選存放的ArrayList內。
3、 比對完之後把你篩選存放的ArrayList丟入FilterResults內。
publishFiltering
1、datalists是在adapter內會變動的陣列,用來顯示RecyclerView用的(filterDatas為固定陣列,用來和filter做資料比對)
2、 把剛剛在performFiltering抓取到的filterResults匯到datalists內。用以顯示recyclerView
3、假如陣列大於0,就notifyDataSetChanged刷新RecyclerView。


DEMO: https://drive.google.com/open?id=0Byk75IYx-dKXNXEzdzlzUEwzc3M

沒有留言:

張貼留言

協程(coroutine) - 協程為什麼要學它?

 Coroutine 協程 再強調一次 協程就是由kotlin官方所提供的線程api //Thread Thread { }.start() //Executor val execu...