Crear un SearchView que se parece a las pautas de diseño del material

Actualmente estoy en el proceso de aprender cómo convertir mi aplicación al diseño de Material y estoy un poco bloqueado en este momento. He añadido la barra de herramientas y he hecho que mi recuadro de navegación superponga todo el contenido.

Ahora estoy tratando de crear una búsqueda ampliable que se parece a la de las pautas materiales : enter image description here

Esto es lo que tengo ahora y no puedo entender cómo hacerlo como lo anterior:
Mi búsqueda

Este es mi menú xml:

    

Eso funciona, obtengo un elemento de menú que se expande a SearchView y puedo filtrar bien mi lista. Aunque no se parece en nada a la primera imagen.

Traté de usar MenuItemCompat.setOnActionExpandListener() en R.id.action_search para poder cambiar el ícono de inicio a una flecha hacia atrás, pero eso no parece funcionar. Nada se dispara en el oyente. Incluso si eso funcionara todavía no estaría muy cerca de la primera imagen.

¿Cómo creo un SearchView en la nueva barra de herramientas de appcompat que se parece a las pautas de material?

En realidad, es bastante fácil hacerlo si usa la biblioteca android.support.v7 .

Paso 1

Declarar un elemento del menú

  

Paso 2

Extienda AppCompatActivity y en onCreateOptionsMenu configure el SearchView.

 import android.support.v7.widget.SearchView; ... public class YourActivity extends AppCompatActivity { ... @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_home, menu); // Retrieve the SearchView and plug it into SearchManager final SearchView searchView = (SearchView) MenuItemCompat.getActionView(menu.findItem(R.id.action_search)); SearchManager searchManager = (SearchManager) getSystemService(SEARCH_SERVICE); searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName())); return true; } ... } 

Resultado

enter image description here

enter image description here

Después de una semana de desconfiar de esto. Creo que lo he descubierto.
Ahora estoy usando solo un EditText dentro de la barra de herramientas. Esto fue sugerido por oj88 en reddit.

Ahora tengo esto:
Nueva búsquedaVer

Primero en onCreate () de mi actividad agregué el EditText con una vista de imagen en el lado derecho de la barra de herramientas de esta manera:

  // Setup search container view searchContainer = new LinearLayout(this); Toolbar.LayoutParams containerParams = new Toolbar.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); containerParams.gravity = Gravity.CENTER_VERTICAL; searchContainer.setLayoutParams(containerParams); // Setup search view toolbarSearchView = new EditText(this); // Set width / height / gravity int[] textSizeAttr = new int[]{android.R.attr.actionBarSize}; int indexOfAttrTextSize = 0; TypedArray a = obtainStyledAttributes(new TypedValue().data, textSizeAttr); int actionBarHeight = a.getDimensionPixelSize(indexOfAttrTextSize, -1); a.recycle(); LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(0, actionBarHeight); params.gravity = Gravity.CENTER_VERTICAL; params.weight = 1; toolbarSearchView.setLayoutParams(params); // Setup display toolbarSearchView.setBackgroundColor(Color.TRANSPARENT); toolbarSearchView.setPadding(2, 0, 0, 0); toolbarSearchView.setTextColor(Color.WHITE); toolbarSearchView.setGravity(Gravity.CENTER_VERTICAL); toolbarSearchView.setSingleLine(true); toolbarSearchView.setImeActionLabel("Search", EditorInfo.IME_ACTION_UNSPECIFIED); toolbarSearchView.setHint("Search"); toolbarSearchView.setHintTextColor(Color.parseColor("#b3ffffff")); try { // Set cursor colour to white // https://stackoverflow.com/a/26544231/1692770 // https://github.com/android/platform_frameworks_base/blob/kitkat-release/core/java/android/widget/TextView.java#L562-564 Field f = TextView.class.getDeclaredField("mCursorDrawableRes"); f.setAccessible(true); f.set(toolbarSearchView, R.drawable.edittext_whitecursor); } catch (Exception ignored) { } // Search text changed listener toolbarSearchView.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { Fragment mainFragment = getFragmentManager().findFragmentById(R.id.container); if (mainFragment != null && mainFragment instanceof MainListFragment) { ((MainListFragment) mainFragment).search(s.toString()); } } @Override public void afterTextChanged(Editable s) { // https://stackoverflow.com/a/6438918/1692770 if (s.toString().length() <= 0) { toolbarSearchView.setHintTextColor(Color.parseColor("#b3ffffff")); } } }); ((LinearLayout) searchContainer).addView(toolbarSearchView); // Setup the clear button searchClearButton = new ImageView(this); Resources r = getResources(); int px = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 16, r.getDisplayMetrics()); LinearLayout.LayoutParams clearParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); clearParams.gravity = Gravity.CENTER; searchClearButton.setLayoutParams(clearParams); searchClearButton.setImageResource(R.drawable.ic_close_white_24dp); // TODO: Get this image from here: https://github.com/google/material-design-icons searchClearButton.setPadding(px, 0, px, 0); searchClearButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { toolbarSearchView.setText(""); } }); ((LinearLayout) searchContainer).addView(searchClearButton); // Add search view to toolbar and hide it searchContainer.setVisibility(View.GONE); toolbar.addView(searchContainer); 

Esto funcionó, pero luego me encontré con un problema donde onOptionsItemSelected () no se llamaba cuando hice tapping en el botón de inicio. Así que no pude cancelar la búsqueda presionando el botón de inicio. Intenté algunas formas diferentes de registrar el oyente click en el botón de inicio, pero no funcionaron.

Eventualmente descubrí que el ActionBarDrawerToggle que tenía interfería con las cosas, así que lo eliminé. Este oyente luego comenzó a trabajar:

  toolbar.setNavigationOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // toolbarHomeButtonAnimating is a boolean that is initialized as false. It's used to stop the user pressing the home button while it is animating and breaking things. if (!toolbarHomeButtonAnimating) { // Here you'll want to check if you have a search query set, if you don't then hide the search box. // My main fragment handles this stuff, so I call its methods. FragmentManager fragmentManager = getFragmentManager(); final Fragment fragment = fragmentManager.findFragmentById(R.id.container); if (fragment != null && fragment instanceof MainListFragment) { if (((MainListFragment) fragment).hasSearchQuery() || searchContainer.getVisibility() == View.VISIBLE) { displaySearchView(false); return; } } } if (mDrawerLayout.isDrawerOpen(findViewById(R.id.navigation_drawer))) mDrawerLayout.closeDrawer(findViewById(R.id.navigation_drawer)); else mDrawerLayout.openDrawer(findViewById(R.id.navigation_drawer)); } }); 

Así que ahora puedo cancelar la búsqueda con el botón de inicio, pero no puedo presionar el botón Atrás para cancelarlo. Así que agregué esto a onBackPressed ():

  FragmentManager fragmentManager = getFragmentManager(); final Fragment mainFragment = fragmentManager.findFragmentById(R.id.container); if (mainFragment != null && mainFragment instanceof MainListFragment) { if (((MainListFragment) mainFragment).hasSearchQuery() || searchContainer.getVisibility() == View.VISIBLE) { displaySearchView(false); return; } } 

Creé este método para alternar la visibilidad de EditText y el elemento del menú:

 public void displaySearchView(boolean visible) { if (visible) { // Stops user from being able to open drawer while searching mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED); // Hide search button, display EditText menu.findItem(R.id.action_search).setVisible(false); searchContainer.setVisibility(View.VISIBLE); // Animate the home icon to the back arrow toggleActionBarIcon(ActionDrawableState.ARROW, mDrawerToggle, true); // Shift focus to the search EditText toolbarSearchView.requestFocus(); // Pop up the soft keyboard new Handler().postDelayed(new Runnable() { public void run() { toolbarSearchView.dispatchTouchEvent(MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, 0, 0, 0)); toolbarSearchView.dispatchTouchEvent(MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, 0, 0, 0)); } }, 200); } else { // Allows user to open drawer again mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED); // Hide the EditText and put the search button back on the Toolbar. // This sometimes fails when it isn't postDelayed(), don't know why. toolbarSearchView.postDelayed(new Runnable() { @Override public void run() { toolbarSearchView.setText(""); searchContainer.setVisibility(View.GONE); menu.findItem(R.id.action_search).setVisible(true); } }, 200); // Turn the home button back into a drawer icon toggleActionBarIcon(ActionDrawableState.BURGER, mDrawerToggle, true); // Hide the keyboard because the search box has been hidden InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(toolbarSearchView.getWindowToken(), 0); } } 

Necesitaba una forma de alternar el botón de inicio en la barra de herramientas entre el icono del cajón y el botón Atrás. Eventualmente encontré el método a continuación en esta respuesta SO . Aunque lo modifiqué ligeramente para que tuviera más sentido para mí:

 private enum ActionDrawableState { BURGER, ARROW } /** * Modified version of this, https://stackoverflow.com/a/26836272/1692770
* I flipped the start offset around for the animations because it seemed like it was the wrong way around to me.
* I also added a listener to the animation so I can find out when the home button has finished rotating. */ private void toggleActionBarIcon(final ActionDrawableState state, final ActionBarDrawerToggle toggle, boolean animate) { if (animate) { float start = state == ActionDrawableState.BURGER ? 1.0f : 0f; float end = Math.abs(start - 1); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { ValueAnimator offsetAnimator = ValueAnimator.ofFloat(start, end); offsetAnimator.setDuration(300); offsetAnimator.setInterpolator(new AccelerateDecelerateInterpolator()); offsetAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { float offset = (Float) animation.getAnimatedValue(); toggle.onDrawerSlide(null, offset); } }); offsetAnimator.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { toolbarHomeButtonAnimating = false; } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } }); toolbarHomeButtonAnimating = true; offsetAnimator.start(); } } else { if (state == ActionDrawableState.BURGER) { toggle.onDrawerClosed(null); } else { toggle.onDrawerOpened(null); } } }

Esto funciona, he logrado resolver algunos errores que encontré en el camino. No creo que sea 100% pero funciona lo suficientemente bien para mí.

EDITAR: Si desea agregar la vista de búsqueda en XML en lugar de Java, haga esto:

toolbar.xml:

       

onCreate () de su actividad:

  searchContainer = findViewById(R.id.search_container); toolbarSearchView = (EditText) findViewById(R.id.search_view); searchClearButton = (ImageView) findViewById(R.id.search_clear); // Setup search container view try { // Set cursor colour to white // https://stackoverflow.com/a/26544231/1692770 // https://github.com/android/platform_frameworks_base/blob/kitkat-release/core/java/android/widget/TextView.java#L562-564 Field f = TextView.class.getDeclaredField("mCursorDrawableRes"); f.setAccessible(true); f.set(toolbarSearchView, R.drawable.edittext_whitecursor); } catch (Exception ignored) { } // Search text changed listener toolbarSearchView.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { Fragment mainFragment = getFragmentManager().findFragmentById(R.id.container); if (mainFragment != null && mainFragment instanceof MainListFragment) { ((MainListFragment) mainFragment).search(s.toString()); } } @Override public void afterTextChanged(Editable s) { } }); // Clear search text when clear button is tapped searchClearButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { toolbarSearchView.setText(""); } }); // Hide the search view searchContainer.setVisibility(View.GONE); 

Sé que es un hilo antiguo, pero todavía publica la biblioteca que acabo de crear. Espero que esto pueda ayudar a alguien.

https://github.com/Shahroz16/material-searchview

Vista de búsqueda de Meterial

La primera captura de pantalla en su pregunta no es un widget público. El soporte SearchView ( android.support.v7.widget.SearchView ) imita Android 5.0 Lollipop’s SearchView ( android.widget.SearchView ). Su segunda captura de pantalla es utilizada por otras aplicaciones diseñadas por materiales como Google Play.

La vista de búsqueda en su primera captura de pantalla se usa en Drive, YouTube y otras aplicaciones de código cerrado de Google. Afortunadamente, también se usa en Android 5.0 Dialer . Puede intentar hacer una copia de seguridad de la vista, pero usa unas 5,0 API.

Las clases que desearás ver son:

SearchEditTextLayout , AnimUtils y DialtactsActivity para comprender cómo usar la vista. También necesitará recursos de ContactsCommon .

La mejor de las suertes.

Aquí está mi bash de hacer esto:

Paso 1: crea un estilo llamado SearchViewStyle

  

Paso 2: simple_search_view_item.xml un diseño llamado simple_search_view_item.xml

   

Paso 3: crea un elemento de menú para esta vista de búsqueda

     

Paso 4: infle el menú

 @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.menu_searchable_activity, menu); return true; } 

Resultado:

enter image description here

Lo único que no pude hacer fue hacer que ocupara todo el ancho de la Toolbar de Toolbar . Si alguien pudiera ayudarme a hacer eso, entonces sería oro.

Para lograr el aspecto deseado de SearchView, puede usar estilos.

En primer lugar, debe crear un style para su SearchView, que debería verse más o menos así:

  

Toda la lista de atributos que puede encontrar en este artículo, en la sección “SearchView”.

En segundo lugar, debe crear un style para su Toolbar , que se usa como Barra de acción:

  

Y finalmente necesita actualizar su atributo de tema de la barra de herramientas de esta manera:

  

Resultado:

enter image description here

NOTA: debe cambiar el atributo de tema de la Toolbar directamente. Si solo actualiza su atributo principal searchViewStyle tema, no afectaría su Toolbar .

Otra forma de lograr el efecto deseado es usar esta biblioteca de Material Search View . Maneja el historial de búsqueda automáticamente y también es posible proporcionar sugerencias de búsqueda a la vista.

Muestra: (Se muestra en portugués, pero también funciona en inglés e italiano).

Muestra

Preparar

Antes de poder usar esta lib, debe implementar una clase llamada MsvAuthority dentro del paquete br.com.mauker en su módulo de aplicación, y debe tener una variable de cadena pública estática llamada CONTENT_AUTHORITY . Déle el valor que desea y no olvide agregar el mismo nombre en su archivo de manifiesto. La biblioteca utilizará este archivo para configurar la autoridad del proveedor de contenido.

Ejemplo:

MsvAuthority.java

 package br.com.mauker; public class MsvAuthority { public static final String CONTENT_AUTHORITY = "br.com.mauker.materialsearchview.searchhistorydatabase"; } 

AndroidManifest.xml

       

Uso

Para usarlo, agregue la dependencia:

 compile 'br.com.mauker.materialsearchview:materialsearchview:1.2.0' 

Y luego, en su archivo de diseño de la Activity , agregue lo siguiente:

  

Después de eso, solo tendrá que obtener la referencia de MaterialSearchView utilizando getViewById() y abrirla o cerrarla usando MaterialSearchView#openSearch() y MaterialSearchView#closeSearch() .

PD: es posible abrir y cerrar la vista no solo desde la Toolbar . Puede utilizar el método openSearch() básicamente desde cualquier Button , como un botón de acción flotante.

 // Inside onCreate() MaterialSearchView searchView = (MaterialSearchView) findViewById(R.id.search_view); Button bt = (Button) findViewById(R.id.button); bt.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { searchView.openSearch(); } }); 

También puede cerrar la vista usando el botón Atrás, haciendo lo siguiente:

 @Override public void onBackPressed() { if (searchView.isOpen()) { // Close the search on the back button press. searchView.closeSearch(); } else { super.onBackPressed(); } } 

Para obtener más información sobre cómo usar lib, consulte la página de github .

Lo siguiente creará un SearchView idéntico al de Gmail y lo agregará a la Barra de herramientas dada. Simplemente deberá implementar su propio método “ViewUtil.convertDpToPixel”.

 private SearchView createMaterialSearchView(Toolbar toolbar, String hintText) { setSupportActionBar(toolbar); ActionBar actionBar = getSupportActionBar(); actionBar.setDisplayHomeAsUpEnabled(true); actionBar.setDisplayShowCustomEnabled(true); actionBar.setDisplayShowTitleEnabled(false); SearchView searchView = new SearchView(this); searchView.setIconifiedByDefault(false); searchView.setMaxWidth(Integer.MAX_VALUE); searchView.setMinimumHeight(Integer.MAX_VALUE); searchView.setQueryHint(hintText); int rightMarginFrame = 0; View frame = searchView.findViewById(getResources().getIdentifier("android:id/search_edit_frame", null, null)); if (frame != null) { LinearLayout.LayoutParams frameParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); rightMarginFrame = ((LinearLayout.LayoutParams) frame.getLayoutParams()).rightMargin; frameParams.setMargins(0, 0, 0, 0); frame.setLayoutParams(frameParams); } View plate = searchView.findViewById(getResources().getIdentifier("android:id/search_plate", null, null)); if (plate != null) { plate.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); plate.setPadding(0, 0, rightMarginFrame, 0); plate.setBackgroundColor(Color.TRANSPARENT); } int autoCompleteId = getResources().getIdentifier("android:id/search_src_text", null, null); if (searchView.findViewById(autoCompleteId) != null) { EditText autoComplete = (EditText) searchView.findViewById(autoCompleteId); LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(0, (int) ViewUtil.convertDpToPixel(36)); params.weight = 1; params.gravity = Gravity.CENTER_VERTICAL; params.leftMargin = rightMarginFrame; autoComplete.setLayoutParams(params); autoComplete.setTextSize(16f); } int searchMagId = getResources().getIdentifier("android:id/search_mag_icon", null, null); if (searchView.findViewById(searchMagId) != null) { ImageView v = (ImageView) searchView.findViewById(searchMagId); v.setImageDrawable(null); v.setPadding(0, 0, 0, 0); LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT); params.setMargins(0, 0, 0, 0); v.setLayoutParams(params); } toolbar.setTitle(null); toolbar.setContentInsetsAbsolute(0, 0); toolbar.addView(searchView); return searchView; }