Java tutorial
package com.rong.library.widget.mapsearchbar; import android.animation.ValueAnimator; import android.annotation.TargetApi; import android.app.Activity; import android.content.Context; import android.content.res.TypedArray; import android.graphics.drawable.Drawable; import android.os.Build; import android.support.annotation.MenuRes; import android.support.v4.content.ContextCompat; import android.support.v7.widget.AppCompatEditText; import android.support.v7.widget.AppCompatImageView; import android.support.v7.widget.CardView; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.PopupMenu; import android.support.v7.widget.RecyclerView; import android.text.Editable; import android.text.TextUtils; import android.text.TextWatcher; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.view.Gravity; import android.view.KeyEvent; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; import android.widget.FrameLayout; import android.widget.RelativeLayout; import android.widget.TextView; import java.util.List; /** * Copyright (c) 2016-2017, j2Rong * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ public class MapSearchBar extends FrameLayout implements View.OnClickListener, PopupMenu.OnMenuItemClickListener, TextView.OnEditorActionListener, View.OnFocusChangeListener, SearchResultAdapter.OnSuggestionItemClickListener { public MapSearchBar(Context context, AttributeSet attrs) { super(context, attrs); init(attrs); } public MapSearchBar(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(attrs); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) public MapSearchBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); init(attrs); } public interface MapSearchActionListener { boolean onSearchBarMenuItemClick(MenuItem item); void onNavigationClick(View v); void onHistoryClick(View v); void onSearchQuerySuggestionClick(SearchResult data); void onSearchConfirmedAsync(String query); void onSearchQueryChangedAsync(String query); } public static final int NAV_MAP_AUTO_NAV = 0; public static final int NAV_MAP_GOOGLE = 1; private CardView cardView = null; private AppCompatImageView btnMenu = null; private AppCompatImageView btnNavigation = null; private AppCompatImageView btnHistory = null; private AppCompatEditText searchEdit = null; private AppCompatImageView btnClear = null; private RelativeLayout containerSuggestion = null; private RecyclerView suggestionRecycler = null; private PopupMenu popupMenu = null; private SearchResultAdapter adapter; private MapSearchActionListener mListener = null; private SearchQueryThread queryThread = null; private int screenHeight = 0; private boolean isSearchEditFocused = false; private boolean focusCameFromHistory = false; private boolean suggestionsVisible; private String hint; private String emptyHistory; private int elevation; private int margin; private int navType; private void init(AttributeSet attrs) { inflate(getContext(), R.layout.search_bar, this); TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.MapSearchBar); hint = a.getString(R.styleable.MapSearchBar_ms_searchHint); emptyHistory = a.getString(R.styleable.MapSearchBar_ms_defaultHistoryEmptyString); elevation = a.getDimensionPixelSize(R.styleable.MapSearchBar_ms_elevation, -1); margin = a.getDimensionPixelSize(R.styleable.MapSearchBar_ms_margin, -1); navType = a.getInt(R.styleable.MapSearchBar_ms_mapType, -1); a.recycle(); // default settings if (hint == null) hint = getContext().getString(R.string.ms_default_edit_hint); if (emptyHistory == null) emptyHistory = ""; if (elevation == -1) elevation = getResources().getDimensionPixelSize(R.dimen.ms_default_search_bar_elevation); if (margin == -1) margin = getResources().getDimensionPixelSize(R.dimen.ms_default_search_bar_margin); if (navType == -1) navType = NAV_MAP_AUTO_NAV; findViews(); setupViews(); DisplayMetrics displaymetrics = new DisplayMetrics(); ((Activity) getContext()).getWindowManager().getDefaultDisplay().getMetrics(displaymetrics); screenHeight = displaymetrics.heightPixels; } private void findViews() { cardView = (CardView) findViewById(R.id.card); btnMenu = (AppCompatImageView) findViewById(R.id.btn_menu); btnNavigation = (AppCompatImageView) findViewById(R.id.btn_nav); btnHistory = (AppCompatImageView) findViewById(R.id.btn_history); searchEdit = (AppCompatEditText) findViewById(R.id.edit); btnClear = (AppCompatImageView) findViewById(R.id.btn_clear); containerSuggestion = (RelativeLayout) findViewById(R.id.list_container); suggestionRecycler = (RecyclerView) findViewById(R.id.recycler); } private void setupViews() { cardView.setCardElevation(elevation); FrameLayout.LayoutParams lpCard = (FrameLayout.LayoutParams) cardView.getLayoutParams(); lpCard.setMargins(margin, margin, margin, margin); cardView.setLayoutParams(lpCard); if (popupMenu == null) btnMenu.setVisibility(GONE); btnHistory.setOnClickListener(this); int navResId = (navType == NAV_MAP_AUTO_NAV) ? R.drawable.a_90 : R.drawable.gmap_24; Drawable d = ContextCompat.getDrawable(getContext(), navResId); btnNavigation.setImageDrawable(d); btnNavigation.setOnClickListener(this); searchEdit.setHint(hint); searchEdit.setOnEditorActionListener(this); searchEdit.setOnFocusChangeListener(this); searchEdit.addTextChangedListener(new SearchEditTextWatcher()); btnClear.setVisibility(GONE); btnClear.setOnClickListener(this); adapter = new SearchResultAdapter(getContext(), this, emptyHistory); suggestionRecycler.setLayoutManager(new LinearLayoutManager(getContext())); suggestionRecycler.setAdapter(adapter); } // // animation // private int getSuggestionHeight() { int h = (int) (screenHeight * 0.45); return adapter.getSuggestedHeight(suggestionRecycler, h); } private void animateSuggestionList(int from, int to) { suggestionsVisible = (to > 0); final ViewGroup.LayoutParams lp = containerSuggestion.getLayoutParams(); if (to == 0 && lp.height == 0) return; // already collapsed if (to == 0) from = lp.height; ValueAnimator animator = ValueAnimator.ofInt(from, to); animator.setDuration(200); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { lp.height = (int) animation.getAnimatedValue(); containerSuggestion.setLayoutParams(lp); } }); animator.start(); } @Override public void onClick(View v) { int id = v.getId(); if (id == R.id.btn_menu) { if (popupMenu != null) { popupMenu.show(); searchEdit.clearFocus(); } } else if (id == R.id.btn_clear) { searchEdit.setText(""); } else if (id == R.id.btn_nav) { if (mListener != null) mListener.onNavigationClick(btnNavigation); } else if (id == R.id.btn_history) { if (mListener != null) { mListener.onHistoryClick(btnHistory); focusCameFromHistory = true; searchEdit.setText(""); searchEdit.requestFocus(); } } } @Override public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { if (actionId == EditorInfo.IME_ACTION_SEARCH) { String q = searchEdit.getText().toString(); searchEdit.setText(""); searchEdit.clearFocus(); if (mListener != null) { if (!TextUtils.isEmpty(q.trim())) mListener.onSearchConfirmedAsync(q); } return true; } return false; } private class SearchEditTextWatcher implements TextWatcher { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void afterTextChanged(Editable s) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { if (TextUtils.isEmpty(s.toString())) { btnClear.setVisibility(GONE); } else { btnClear.setVisibility(VISIBLE); } if (queryThread != null) { queryThread.pushQuery(s.toString()); } } } @Override public void onFocusChange(View v, boolean hasFocus) { InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); if (hasFocus) { if (!focusCameFromHistory) imm.showSoftInput(v, InputMethodManager.SHOW_IMPLICIT); focusCameFromHistory = false; isSearchEditFocused = true; if (adapter.getItemCount() > 0 && !suggestionsVisible) animateSuggestionList(0, getSuggestionHeight()); } else { imm.hideSoftInputFromWindow(v.getWindowToken(), 0); isSearchEditFocused = false; if (suggestionsVisible) animateSuggestionList(-1, 0); } } @SuppressWarnings("SimplifiableIfStatement") @Override public boolean onMenuItemClick(MenuItem item) { if (mListener != null) return mListener.onSearchBarMenuItemClick(item); else return false; } @Override public void onSuggestionItemClick(int position, View v, SearchResult data) { if (mListener != null) mListener.onSearchQuerySuggestionClick(data); searchEdit.setText(""); searchEdit.clearFocus(); } /** * update search result list * @param list new search result list, null if only content changed * @param theQuery query of this search * */ public void updateSearchResult(List<SearchResult> list, String theQuery) { int previous, current; previous = getSuggestionHeight(); if (list != null) adapter.setList(list, theQuery); adapter.notifyDataSetChanged(); current = getSuggestionHeight(); if (isSearchEditFocused) animateSuggestionList(previous, current); } public void inflateMenu(@MenuRes int menuRes) { if (menuRes > 0) { btnMenu.setVisibility(VISIBLE); btnMenu.setOnClickListener(this); popupMenu = new PopupMenu(getContext(), btnMenu); popupMenu.setOnMenuItemClickListener(this); popupMenu.inflate(menuRes); popupMenu.setGravity(Gravity.END); } } public void setMapSearchActionListener(MapSearchActionListener l) { mListener = l; if (queryThread != null) { queryThread.updateListener(mListener); } } public void startQueryThread() { if (queryThread == null) { queryThread = new SearchQueryThread(); queryThread.start(); } } public void stopQueryThread() { if (queryThread != null) { queryThread.interrupt(); queryThread = null; } } public void setNavigationType(int type) { navType = type; if (type != NAV_MAP_AUTO_NAV && type != NAV_MAP_GOOGLE) navType = NAV_MAP_AUTO_NAV; int navResId = (navType == NAV_MAP_AUTO_NAV) ? R.drawable.a_90 : R.drawable.gmap_24; Drawable d = ContextCompat.getDrawable(getContext(), navResId); btnNavigation.setImageDrawable(d); } }