Grupos enlazados en ExpandableListView

¿Existe una manera estándar de fijar el elemento de grupo a la parte superior de la pantalla mientras se desplazan los elementos del grupo. Vi ejemplos similares con ListView. ¿Qué interfaces debo implementar o qué métodos reemplazan?

Encontré una solución basada en Pinned Header ListView de Peter Kuterna y Android sample

package com.example; import com.example.ExpandableList.MyExpandableListAdapter; import android.content.Context; import; import android.util.AttributeSet; import android.view.View; import android.widget.BaseExpandableListAdapter; import android.widget.ExpandableListAdapter; import android.widget.ExpandableListView; import android.widget.TextView; /** * A ListView that maintains a header pinned at the top of the list. The * pinned header can be pushed up and dissolved as needed. */ public class PinnedHeaderExpListView extends ExpandableListView{ /** * Adapter interface. The list adapter must implement this interface. */ public interface PinnedHeaderAdapter { /** * Pinned header state: don't show the header. */ public static final int PINNED_HEADER_GONE = 0; /** * Pinned header state: show the header at the top of the list. */ public static final int PINNED_HEADER_VISIBLE = 1; /** * Pinned header state: show the header. If the header extends beyond * the bottom of the first shown element, push it up and clip. */ public static final int PINNED_HEADER_PUSHED_UP = 2; /** * Configures the pinned header view to match the first visible list item. * * @param header pinned header view. * @param position position of the first visible list item. * @param alpha fading of the header view, between 0 and 255. */ void configurePinnedHeader(View header, int position, int alpha); } private static final int MAX_ALPHA = 255; private MyExpandableListAdapter mAdapter; private View mHeaderView; private boolean mHeaderViewVisible; private int mHeaderViewWidth; private int mHeaderViewHeight; public PinnedHeaderExpListView(Context context) { super(context); } public PinnedHeaderExpListView(Context context, AttributeSet attrs) { super(context, attrs); } public PinnedHeaderExpListView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } public void setPinnedHeaderView(View view) { mHeaderView = view; // Disable vertical fading when the pinned header is present // TODO change ListView to allow separate measures for top and bottom fading edge; // in this particular case we would like to disable the top, but not the bottom edge. if (mHeaderView != null) { setFadingEdgeLength(0); } requestLayout(); } @Override public void setAdapter(ExpandableListAdapter adapter) { super.setAdapter(adapter); mAdapter = (MyExpandableListAdapter)adapter; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); if (mHeaderView != null) { measureChild(mHeaderView, widthMeasureSpec, heightMeasureSpec); mHeaderViewWidth = mHeaderView.getMeasuredWidth(); mHeaderViewHeight = mHeaderView.getMeasuredHeight(); } } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); if (mHeaderView != null) { mHeaderView.layout(0, 0, mHeaderViewWidth, mHeaderViewHeight); configureHeaderView(getFirstVisiblePosition()); } } /** * animating header pushing * @param position */ public void configureHeaderView(int position) { final int group = getPackedPositionGroup(getExpandableListPosition(position)); int groupView = getFlatListPosition(getPackedPositionForGroup(group)); if (mHeaderView == null) { return; } mHeaderView.setOnClickListener(new OnClickListener() { public void onClick(View header) { if(!expandGroup(group)) collapseGroup(group); } }); int state,nextSectionPosition = getFlatListPosition(getPackedPositionForGroup(group+1)); if (mAdapter.getGroupCount()== 0) { state = PinnedHeaderAdapter.PINNED_HEADER_GONE; }else if (position < 0) { state = PinnedHeaderAdapter.PINNED_HEADER_GONE; }else if (nextSectionPosition != -1 && position == nextSectionPosition - 1) { state=PinnedHeaderAdapter.PINNED_HEADER_PUSHED_UP; }else state=PinnedHeaderAdapter.PINNED_HEADER_VISIBLE; switch (state) { case PinnedHeaderAdapter.PINNED_HEADER_GONE: { mHeaderViewVisible = false; break; } case PinnedHeaderAdapter.PINNED_HEADER_VISIBLE: { mAdapter.configurePinnedHeader(mHeaderView, group, MAX_ALPHA); if (mHeaderView.getTop() != 0) { mHeaderView.layout(0, 0, mHeaderViewWidth, mHeaderViewHeight); } mHeaderViewVisible = true; break; } case PinnedHeaderAdapter.PINNED_HEADER_PUSHED_UP: { View firstView = getChildAt(0); if(firstView==null){ if (mHeaderView.getTop() != 0) { mHeaderView.layout(0, 0, mHeaderViewWidth, mHeaderViewHeight); } mHeaderViewVisible = true; break; } int bottom = firstView.getBottom(); int itemHeight = firstView.getHeight(); int headerHeight = mHeaderView.getHeight(); int y; int alpha; if (bottom < headerHeight) { y = (bottom - headerHeight); alpha = MAX_ALPHA * (headerHeight + y) / headerHeight; } else { y = 0; alpha = MAX_ALPHA; } mAdapter.configurePinnedHeader(mHeaderView, group, alpha); //выползание if (mHeaderView.getTop() != y) { mHeaderView.layout(0, y, mHeaderViewWidth, mHeaderViewHeight + y); } mHeaderViewVisible = true; break; } } } @Override protected void dispatchDraw(Canvas canvas) { super.dispatchDraw(canvas); if (mHeaderViewVisible) { drawChild(canvas, mHeaderView, getDrawingTime()); } } }

 package com.example; import; import; import android.os.Bundle; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.AbsListView; import android.widget.AbsListView.LayoutParams; import android.widget.AbsListView.OnScrollListener; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.BaseExpandableListAdapter; import android.widget.ExpandableListAdapter; import android.widget.SectionIndexer; import android.widget.TextView; import android.widget.Toast; import com.example.PinnedHeaderExpListView.PinnedHeaderAdapter; /** * Demonstrates expandable lists using a custom {@link ExpandableListAdapter} * from {@link BaseExpandableListAdapter}. */ public class ExpandableList extends Activity { MyExpandableListAdapter mAdapter; PinnedHeaderExpListView elv; private int mPinnedHeaderBackgroundColor; private int mPinnedHeaderTextColor; public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); // Set up our adapter mAdapter = new MyExpandableListAdapter(); elv = (PinnedHeaderExpListView) findViewById(; elv.setAdapter(mAdapter); mPinnedHeaderBackgroundColor = getResources().getColor(; mPinnedHeaderTextColor = getResources().getColor(android.R.color.white); elv.setGroupIndicator(null); View h = LayoutInflater.from(this).inflate(R.layout.header, (ViewGroup) findViewById(, false); elv.setPinnedHeaderView(h); elv.setOnScrollListener((OnScrollListener) mAdapter); elv.setDividerHeight(0); } /** * A simple adapter which maintains an ArrayList of photo resource Ids. * Each photo is displayed as an image. This adapter supports clearing the * list of photos and adding a new photo. * */ public class MyExpandableListAdapter extends BaseExpandableListAdapter implements PinnedHeaderAdapter, OnScrollListener{ // Sample data set. children[i] contains the children (String[]) for groups[i]. private String[] groups = { "People Names", "Dog Names", "Cat Names", "Fish Names" }; private String[][] children = { { "Arnold", "Barry", "Chuck", "David", "Stas", "Oleg", "Max","Alex","Romeo", "Adolf" }, { "Ace", "Bandit", "Cha-Cha", "Deuce", "Nokki", "Baron", "Sharik", "Toshka","SObaka","Belka","Strelka","Zhuchka"}, { "Fluffy", "Snuggles","Cate", "Yasha","Bars" }, { "Goldy", "Bubbles","Fluffy", "Snuggles","Guffy", "Snoopy" } }; public Object getChild(int groupPosition, int childPosition) { return children[groupPosition][childPosition]; } public long getChildId(int groupPosition, int childPosition) { return childPosition; } public int getChildrenCount(int groupPosition) { return children[groupPosition].length; } public TextView getGenericView() { // Layout parameters for the ExpandableListView AbsListView.LayoutParams lp = new AbsListView.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, 64); TextView textView = new TextView(ExpandableList.this); textView.setLayoutParams(lp); // Center the text vertically textView.setGravity(Gravity.CENTER_VERTICAL | Gravity.LEFT); // Set the text starting position textView.setPadding(36, 0, 0, 0); return textView; } public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) { TextView textView = getGenericView(); textView.setText(getChild(groupPosition, childPosition).toString()); return textView; } public Object getGroup(int groupPosition) { return groups[groupPosition]; } public int getGroupCount() { return groups.length; } public long getGroupId(int groupPosition) { return groupPosition; } public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) { TextView textView = (TextView) LayoutInflater.from(getApplicationContext()).inflate(R.layout.header, parent, false); textView.setText(getGroup(groupPosition).toString()); return textView; } public boolean isChildSelectable(int groupPosition, int childPosition) { return true; } public boolean hasStableIds() { return true; } /** * размытие/пропадание хэдера */ public void configurePinnedHeader(View v, int position, int alpha) { TextView header = (TextView) v; final String title = (String) getGroup(position); header.setText(title); if (alpha == 255) { header.setBackgroundColor(mPinnedHeaderBackgroundColor); header.setTextColor(mPinnedHeaderTextColor); } else { header.setBackgroundColor(Color.argb(alpha,,,; header.setTextColor(Color.argb(alpha,,,; } } public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { if (view instanceof PinnedHeaderExpListView) { ((PinnedHeaderExpListView) view).configureHeaderView(firstVisibleItem); } } public void onScrollStateChanged(AbsListView view, int scrollState) { // TODO Auto-generated method stub } } } 


 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="" android:id="@+id/root" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" > <view class="com.example.PinnedHeaderExpListView" android:id="@+id/list" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout> 


 <?xml version="1.0" encoding="utf-8"?> <TextView xmlns:android="" android:id="@+id/header" android:layout_width="match_parent" android:layout_height="25dp" android:background="@android:color/black" > </TextView> 

Funciona como se esperaba, excepto haciendo clic en el encabezado. Ojalá que hacer clic en el encabezado será igual haciendo clic en el elemento de grupo, pero el evento ni siquiera sucede y OnClickListener no obtiene el control. Por qué ?

EDIT: haciendo clic en el encabezado también funciona si agrega el siguiente código dentro del método onCreate() de la actividad .

  elv.setOnGroupClickListener(new OnGroupClickListener() { @Override public boolean onGroupClick(ExpandableListView parent, View v, int groupPosition, long id) { return false; } }); 

He intentado implementar la solución de Homo Incognito y encontrar el mismo problema de encabezado pinned que no se puede hacer clic.

El culpable parece ser el propio ListView que consume todo el evento de clic, por lo que no pasa a nuestra vista de cabecera "aumentada". Así que podría intentar excavar en la implementación de manipulación de clics de ExpandableListView, que es un desastre, teniendo en cuenta su herencia hasta el punto de vista de AdapterView.

En vez de intentar highjack el tecleo de ListView, intento circumnavigating tal edición simulando el tecleo del cabecero del artículo de la lista debajo de él. Para ello, debe implementar el onChildClick para ver primero si la posición del elemento que se ha hecho clic está debajo del encabezado fijado, de ser así, puede transferir el clic al encabezado verdadero, en caso contrario, sólo procese el clic para que Normalmente.

En el ejemplo siguiente, cuando el elemento que se ha hecho clic está debajo del encabezado fijado, simplemente hice el desplazamiento de ListView al encabezado verdadero, reemplazando así el encabezado fijado "aumentado" con el encabezado verdadero y el usuario puede tomar más acciones desde allí, por ejemplo Colapsando el grupo.

Tenga en cuenta que este flujo de usabilidad sólo funciona si no tiene ninguna vista que haga clic en los elementos de encabezado, de lo contrario tendrá que hacer todo el clic de retransmisión y mapeo entre el encabezado virtual fijado y el encabezado verdadero con onInterceptTouchEvent .

Siguiendo el código de Homo Incognito, en agregue el siguiente método:

 public int getHeaderViewHeight(){ return mHeaderViewHeight; } 

En la actividad onCreate , agregue lo siguiente:

 elv.setOnChildClickListener(new ExpandableListView.OnChildClickListener() { @Override public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) { // we need to obtain the relative y coordinate of the child view, // not its clicked subview, thus first we try to calculate its true index long packedPos = ExpandableListView.getPackedPositionForChild(groupPosition, childPosition); int viewPos = elv.getFlatListPosition(packedPos) - elv.getFirstVisiblePosition(); View childView = parent.getChildAt(viewPos); // got it if (childView.getTop() < elv.getHeaderViewHeight()*.75){ // if the clicked child item overlaps more than 25% // of pinned header, consider it being underneath long groupPackedPos = ExpandableListView.getPackedPositionForGroup(groupPosition); int groupFlatPos = elv.getFlatListPosition(groupPackedPos); elv.smoothScrollToPosition(groupFlatPos); } return true; } }); 

En la respuesta de Homo Incognito, la vista de niño en vista de cabecera no puede hacer clic y recibir el evento de clic, pero encontré una manera. Puse el código en:

 private final Rect mRect = new Rect(); private final int[] mLocation = new int[2]; @Override public boolean dispatchTouchEvent(MotionEvent ev) { if (mHeaderView == null) return super.dispatchTouchEvent(ev); if (mHeaderViewVisible) { final int x = (int) ev.getX(); final int y = (int) ev.getY(); mHeaderView.getLocationOnScreen(mLocation); mRect.left = mLocation[0]; = mLocation[1]; mRect.right = mLocation[0] + mHeaderView.getWidth(); mRect.bottom = mLocation[1] + mHeaderView.getHeight(); if (mRect.contains(x, y)) { if (ev.getAction() == MotionEvent.ACTION_UP) { performViewClick(x, y); } return true; } else { return super.dispatchTouchEvent(ev); } } else { return super.dispatchTouchEvent(ev); } } private void performViewClick(int x, int y) { if (null == mHeaderView) return; final ViewGroup container = (ViewGroup) mHeaderView; for (int i = 0; i < container.getChildCount(); i++) { View view = container.getChildAt(i); /** * transform coordinate to find the child view we clicked * getGlobalVisibleRect used for android 2.x, getLocalVisibleRect * user for 3.x or above, maybe it's a bug */ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { view.getGlobalVisibleRect(mRect); } else { view.getLocalVisibleRect(mRect); int width = mRect.right - mRect.left; mRect.left = Math.abs(mRect.left); mRect.right = mRect.left + width; } if (mRect.contains(x, y)) { view.performClick(); break; } } } 

Esta es la forma en que manejo el evento de clic en la vista fijada, reemplaza dispatchTouchEvent.

Introduzca aquí la descripción de la imagen


 public void setSelectedPosition(int position){ this.listChildPosition=position; } 

En getchildview

  if (listChildPosition == childPosition) { convertView.setBackgroundColor(context.getResources().getColor( R.color.white)); } else { convertView.setBackgroundColor(context.getResources().getColor( R.color.expandlist)); } 

En onChildClick

  adapter.setSelectedPosition(childPosition); adapter.notifyDataSetChanged(); v.setSelected(true); 
  • Widgets de Android: Cómo agregar vistas a RemoteViews en tiempo de ejecución
  • El fondo de la vista ampliable se vuelve negro cuando se amplía
  • Vista de calibre simple como speedmeter en android?
  • Cómo obtener un Gesture View como en la aplicación oficial de Facebook
  • FilePicker desde android webview trabajando sólo una vez
  • Vista web de Android ui
  • Cómo cambiar el tamaño de AlertDialog en la pantalla del teclado
  • Actualizando ExpandableListView con notifyDataSetChanged ()
  • El método setLayoutParams () no funciona
  • Analógico de android: filterTouchesWhenObscured para el nivel de API inferior a 9
  • Obtener Vista para el elemento ListView / orden inversa en 2.2; Funciona en 4.0.3
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.