FloatingSearchView 的使用

FloatingSearchView 的使用

最近在工程中需要用到一个搜索栏,对于谷歌的设计规范中,谷歌是推荐了两种搜索栏

img

img

在经过抉择之后,我准备要采用第一种设计的搜索栏。对于谷歌的规范,我本以为谷歌会提供官方的库文件来给我们使用,最后却神奇的发现没有,但却给我找到了一个基本功能不逊于官方的一个控件,而对于该控件的使用,网上却没有相应的教程,本文就来讲诉如何基础地使用该控件。

控件地址:https://github.com/arimorty/floatingsearchview

本文只介绍该控件的一种基本用法,更多更深层的用法需要读者自我摸索,并欢迎交流。

引入库

该控件的引入很简单,只要在你的dependencies中加入 compile 'com.github.arimorty:floatingsearchview:2.0.3' 即可,然后增加 FloatingSearchView 到你相应的视图层级中。这里必须强调一点,该控件的宽高必须是占据整个屏幕的。之后,只用在代码中监听查询字符的变动以及提供搜索建议项即可,该内容为重点内容。

示例:

       <com.arlib.floatingsearchview.FloatingSearchView
                android:id="@+id/floating_search_view"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                app:floatingSearch_searchBarMarginLeft="@dimen/search_view_inset"
                app:floatingSearch_searchBarMarginTop="@dimen/search_view_inset"
                app:floatingSearch_searchBarMarginRight="@dimen/search_view_inset"
                app:floatingSearch_searchHint="Search..."
                app:floatingSearch_suggestionsListAnimDuration="250"
                app:floatingSearch_showSearchKey="false"
                app:floatingSearch_leftActionMode="showHamburger"
                app:floatingSearch_menu="@menu/menu_main"
                app:floatingSearch_close_search_on_keyboard_dismiss="true"/>

这个控件含有的属性还是比较多的,很多的功能已经在名称中有所体现,这里主要介绍几个:

  • app:floatingSearch_leftActionMode

    功能是设置搜索栏左侧的按钮,有四种属性值可以选择:

    1. showHanburger
      img
    2. showSearch
      img
    3. showHome
      img
    4. noLeftAction
      img

    而相应的模式的监听函数也是不同的,我们一般只会监听 hamburger 与 home 类型的按钮。如果是监听 hamburger 类型的点击:

    mSearchView.setOnLeftMenuClickListener(
          new FloatingSearchView.OnLeftMenuClickListener() { ...} );  

    hamburger 一般用来打开 NavigationDrawer,所以该控件也提供了一个函数用来快速连接,非常的方便。

    mSearchView.attachNavigationDrawerToMenuButton(mDrawerLayout);

    如果是监听 home 类型的点击事件:

    mSearchView.setOnHomeActionClickListener(
           new FloatingSearchView.OnHomeActionClickListener() { ... });  
  • app:floatingSearch_menu

    很明显,用来配置菜单项,这里注意一下item的 app:showAsAction 的属性值:

    never:永远保持在弹出菜单中

    ifRoom:除了如果有空位能够显示以外,还需要搜索栏不是在点击状态,如果搜索栏在点击状态的话会消失

    always:永远显示

    对于菜单项的点击监听事件:

    mSearchView.setOnMenuItemClickListener(new FloatingSearchView.OnMenuItemClickListener() {
        @Override
        public void onMenuItemSelected(MenuItem item) {                  
    
        }
     });

其余的属性值,读者可以自行摸索,还是比较直接明了能发现功能的。

搜索推荐项

大家在使用搜索功能的时候,在输入几个字符后,会发现出现相应的推荐项来给我们选择,来方便我们。而该控件在这一方面也做得非常好。

推荐项类

很显然,我们需要编写一个推荐项类帮助该控件认识该如何显示推荐项。而该控件已经提供了我们一个接口用来实现推荐项。

public interface SearchSuggestion extends Parcelable{

    /**
     * Returns the text that should be displayed
     * for the suggestion represented by this object.
     *
     * @return the text for this suggestion
     */
    String getBody();

}

这个接口很简单,也太简单了,所以大部分事情还是需要我们来实现了。这里只是举一个例子,在实际工程中还需要大家举一反三,首先定义一个类,比如我们要做的是一个颜色搜索的方面,所以提供的建议项就提供相应颜色的名字即可。

public class ColorSuggestion implements SearchSuggestion {

    private String mColorName;
    private boolean mIsHistory = false;

    public ColorSuggestion(String suggestion) {
        this.mColorName = suggestion.toLowerCase();
    }

    public ColorSuggestion(Parcel source) {
        this.mColorName = source.readString();
        this.mIsHistory = source.readInt() != 0;
    }

    public void setIsHistory(boolean isHistory) {
        this.mIsHistory = isHistory;
    }

    public boolean getIsHistory() {
        return this.mIsHistory;
    }

    @Override
    public String getBody() {
        return mColorName;
    }

    public static final Creator<ColorSuggestion> CREATOR = new Creator<ColorSuggestion>() {
        @Override
        public ColorSuggestion createFromParcel(Parcel in) {
            return new ColorSuggestion(in);
        }

        @Override
        public ColorSuggestion[] newArray(int size) {
            return new ColorSuggestion[size];
        }
    };

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(mColorName);
        dest.writeInt(mIsHistory ? 1 : 0);
    }
}

代码非常简单,很多篇幅只是为了实现Parcel接口而已。而 mIsHistory 的作用是来表明是否是历史搜索项,因为搜索栏一般也有显示出历史搜索项的功能。而其他变量就是要显示在搜索推荐项的一些东西,建议不要太多,影响美观。

帮助类

有了推荐项类,我们需要做的只是在数据中找到推荐项即可。所以主要的工作在这个类上进行。定义类 DataHelper 并在类中初始化相应的数据,该步就是需要具体工程具体实现的一步了,代码仅供参考。

public class DataHelper {

    private static final String COLORS_FILE_NAME = "colors.json";

    private static List<ColorWrapper> sColorWrappers = new ArrayList<>();

    private static List<ColorSuggestion> sColorSuggestions =
            new ArrayList<>(Arrays.asList(
                    new ColorSuggestion("green"),
                    new ColorSuggestion("blue"),
                    new ColorSuggestion("pink"),
                    new ColorSuggestion("purple"),
                    new ColorSuggestion("brown"),
                    new ColorSuggestion("gray"),
                    new ColorSuggestion("Granny Smith Apple"),
                    new ColorSuggestion("Indigo"),
                    new ColorSuggestion("Periwinkle"),
                    new ColorSuggestion("Mahogany"),
                    new ColorSuggestion("Maize"),
                    new ColorSuggestion("Mahogany"),
                    new ColorSuggestion("Outer Space"),
                    new ColorSuggestion("Melon"),
                    new ColorSuggestion("Yellow"),
                    new ColorSuggestion("Orange"),
                    new ColorSuggestion("Red"),
new ColorSuggestion("Orchid")));


    private static void initColorWrapperList(Context context) {

        if (sColorWrappers.isEmpty()) {
            String jsonString = loadJson(context);
            sColorWrappers = deserializeColors(jsonString);
        }
    }

    private static String loadJson(Context context) {

        String jsonString;

        try {
            InputStream is = context.getAssets().open(COLORS_FILE_NAME);
            int size = is.available();
            byte[] buffer = new byte[size];
            is.read(buffer);
            is.close();
            jsonString = new String(buffer, "UTF-8");
        } catch (IOException ex) {
            ex.printStackTrace();
            return null;
        }

        return jsonString;
    }

    private static List<ColorWrapper> deserializeColors(String jsonString) {

        Gson gson = new Gson();

        Type collectionType = new TypeToken<List<ColorWrapper>>() {
        }.getType();
        return gson.fromJson(jsonString, collectionType);
    }

}

之后我们需要在类内定义两个接口,用来作为找到推荐项或者是直接找到了我们需要的颜色的回调函数。

    public interface OnFindColorsListener {
        void onResults(List<ColorWrapper> results);
    }

    public interface OnFindSuggestionsListener {
        void onResults(List<ColorSuggestion> results);
}

现在就是大头了,通过搜索的字符来寻找相应的推荐项和颜色,先来找找推荐项,两者的原理其实是差不多的,该控件的处理方式是找到推荐项后点击了相应的推荐项后再根据推荐项的内容来找到相应的具体颜色。

public static void findSuggestions(Context context, String query, final int limit, final long simulatedDelay,
                                       final OnFindSuggestionsListener listener) {
        new Filter() {

            @Override
            protected FilterResults performFiltering(CharSequence constraint) {

                try {
                    Thread.sleep(simulatedDelay);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                DataHelper.resetSuggestionsHistory();
                List<ColorSuggestion> suggestionList = new ArrayList<>();
                if (!(constraint == null || constraint.length() == 0)) {

                    for (ColorSuggestion suggestion : sColorSuggestions) {
                        if (suggestion.getBody().toUpperCase()
                                .startsWith(constraint.toString().toUpperCase())) {

                            suggestionList.add(suggestion);
                            if (limit != -1 && suggestionList.size() == limit) {
                                break;
                            }
                        }
                    }
                }

                FilterResults results = new FilterResults();
                Collections.sort(suggestionList, new Comparator<ColorSuggestion>() {
                    @Override
                    public int compare(ColorSuggestion lhs, ColorSuggestion rhs) {
                        return lhs.getIsHistory() ? -1 : 0;
                    }
                });
                results.values = suggestionList;
                results.count = suggestionList.size();

                return results;
            }

            @Override
            protected void publishResults(CharSequence constraint, FilterResults results) {

                if (listener != null) {
                    listener.onResults((List<ColorSuggestion>) results.values);
                }
            }
        }.filter(query);

    }

这里主要采用的是一个筛选器,先故意等待了一段时间来为搜索数据库等来模拟,之后出现了一个 resetSuggestionsHistory() 函数,该函数还未讲解,但是字面意思很明显就是将搜索历史纪录全部清空。然后就是一个筛选了,从所有的推荐项中选出前缀和搜索字符相同的推荐项,当然还有其他的模糊搜索方式,就需要读者自行处理相应的代码了。在后就是一个对于已筛选出来的推荐项来一个排序后存入筛选后结果 results 变量里返回即可。

所以搜索具体项的函数也很明了了:

public static void findColors(Context context, String query, final OnFindColorsListener listener) {
        initColorWrapperList(context);

        new Filter() {

            @Override
            protected FilterResults performFiltering(CharSequence constraint) {


                List<ColorWrapper> suggestionList = new ArrayList<>();

                if (!(constraint == null || constraint.length() == 0)) {

                    for (ColorWrapper color : sColorWrappers) {
                        if (color.getName().toUpperCase()
                                .startsWith(constraint.toString().toUpperCase())) {

                            suggestionList.add(color);
                        }
                    }

                }

                FilterResults results = new FilterResults();
                results.values = suggestionList;
                results.count = suggestionList.size();

                return results;
            }

            @Override
            protected void publishResults(CharSequence constraint, FilterResults results) {

                if (listener != null) {
                    listener.onResults((List<ColorWrapper>) results.values);
                }
            }
        }.filter(query);

}

可以发现,就是一样的嘛,但是还是具体情况具体分析。然后就是两个对于历史搜索项的处理,这里的处理很粗糙,大家也是自己举一反三一样。

    public static List<ColorSuggestion> getHistory(Context context, int count) {

        List<ColorSuggestion> suggestionList = new ArrayList<>();
        ColorSuggestion colorSuggestion;
        for (int i = 0; i < sColorSuggestions.size(); i++) {
            colorSuggestion = sColorSuggestions.get(i);
            colorSuggestion.setIsHistory(true);
            suggestionList.add(colorSuggestion);
            if (suggestionList.size() == count) {
                break;
            }
        }
        return suggestionList;
    }

    public static void resetSuggestionsHistory() {
        for (ColorSuggestion colorSuggestion : sColorSuggestions) {
            colorSuggestion.setIsHistory(false);
        }
}

这样的话,帮助类就大功告成了,之后我们需要做就是在activity中运用起这个帮助类。

在Activity中使用

在activity中,控件给予了我们很多好用的回调函数来应对各种情况,下面我们就来仔细说说,首先我们当然要获取 FloatingSearchView 这一控件,并定义一个 mLastQuery 的String变量来储存最近一次的搜索字符。然后我们来一个个介绍基本的回调函数:

setOnQueryChangeListener

很显然用来检测搜索字符变化的监听器

private void setupFloatingSearch() {
        mSearchView.setOnQueryChangeListener(new FloatingSearchView.OnQueryChangeListener() {

            @Override
            public void onSearchTextChanged(String oldQuery, final String newQuery) {

                if (!oldQuery.equals("") && newQuery.equals("")) {
                    mSearchView.clearSuggestions();
                } else {
            DataHelper.findSuggestions(getActivity(), newQuery, 5,
                            FIND_SUGGESTION_SIMULATED_DELAY, new DataHelper.OnFindSuggestionsListener() {

                                @Override
                                public void onResults(List<ColorSuggestion> results) {
         mSearchView.hideProgress();
                                }
                            });
                }
            }
        });

首先判断原来的搜索字符不为空但是现在的搜索字符为空了,说明删除了所有的搜索字符,于是清空推荐项。如果还有搜索字符,首先显示出一个圆形的加载动画,然后调用我们写的帮助类的推荐项搜索函数即可,在搜索函数中我们需要重写接口,在接口中我们展示了推荐项与关闭了加载动画,这样的话实时出现推荐项的搜索功能基本完成了。

setOnSearchListener

用来处理点击事件

 mSearchView.setOnSearchListener(new FloatingSearchView.OnSearchListener() {
            @Override
            public void onSuggestionClicked(final SearchSuggestion searchSuggestion) {

                mLastQuery = searchSuggestion.getBody();
            }

            @Override
            public void onSearchAction(String query) {
                mLastQuery = query;

                Log.d(TAG, "onSearchAction()");
            }
});

如果你在xml中设置了键盘的回车键改为搜索键的话,就需要监听两个点击事件,而该函数已经包括来两个点击事件,第一个为搜索推荐项的点击事件,第二个为键盘直接点击搜索按钮的点击事件,具体的事件处理由读者自己完成,这里只是写了两个必要语句用来更新最近一次的搜索字符。

setOnFocusChangeListener

用来处理搜索栏获得与取消焦点的事件监听

mSearchView.setOnFocusChangeListener(new FloatingSearchView.OnFocusChangeListener() {
            @Override
            public void onFocus() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        // 展示历史搜索项
                        mSearchView.swapSuggestions(DataHelper.getHistory(getActivity(), 3));

                    }
            }

            @Override
            public void onFocusCleared() {
            mSearchView.setSearchBarTitle(mLastQuery);

                //你也可以将已经打上的搜索字符保存,以致在下一次点击的时候,搜索栏内还保存着之前输入的字符
                //mSearchView.setSearchText(searchSuggestion.getBody());

            }
});

setOnMenuItemClickListener

很显然,如果有相应的菜单项的,菜单项的点击事件,这里就不多说明了

setOnHomeActionClickListener

用来处理Home按钮的点击事件,也不多说明

setOnBindSuggestionCallback

这个函数就是用来自定义推荐项的样式的关键函数

mSearchView.setOnBindSuggestionCallback(new SearchSuggestionsAdapter.OnBindSuggestionCallback() {
            @Override
            public void onBindSuggestion(View suggestionView, ImageView leftIcon,
                                         TextView textView, SearchSuggestion item, int itemPosition) {
                ColorSuggestion colorSuggestion = (ColorSuggestion) item;

                String textColor = mIsDarkSearchTheme ? "#ffffff" : "#000000";
                String textLight = mIsDarkSearchTheme ? "#bfbfbf" : "#787878";

                if (colorSuggestion.getIsHistory()) {
                    leftIcon.setImageDrawable(ResourcesCompat.getDrawable(getResources(),
                            R.drawable.ic_history_black_24dp, null));

                    Util.setIconColor(leftIcon, Color.parseColor(textColor));
                    leftIcon.setAlpha(.36f);
                } else {
                    leftIcon.setAlpha(0.0f);
                    leftIcon.setImageDrawable(null);
                }

                textView.setTextColor(Color.parseColor(textColor));
                String text = colorSuggestion.getBody()
                        .replaceFirst(mSearchView.getQuery(),
                                "<font color=\"" + textLight + "\">" + mSearchView.getQuery() + "</font>");
                textView.setText(Html.fromHtml(text));
            }

});

在这个函数你可以得到推荐项,然后根据推荐项的成员变量来做一些样式上的改变,发挥你们的想象力吧,但是不要太繁杂。

配置完这些后,搜索栏就可以畅快的使用啦

在最后

该控件其实有三种模式,并还拥有夜间模式,具体的内容,大家可以查看他们的github网站来研究他们给出的案例,希望大家在使用这个控件能获得便利,欢迎讨论。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值