Intereting Posts
Flurry Agent, вызывающий исключение CalledFromWrongThreadException на Android 2.3.4 Как создать SDK Android со скрытыми и внутренними API-интерфейсами? Передача настраиваемого добавочного объекта дополнительно или в ArrayList для RemoteViewsService прерывает приложение Динамические изменения макетов, чем статические макеты Неправильное положение шкалы после применения эффекта масштабирования на холсте Android В чем разница между ImageView.ScaleType.CENTER_INSIDE и FIT_CENTER? Как совершать пропущенные звонки? Как реализовать кнопку в ViewPager? If (session.isOpen ()), facebook login на android всегда возвращает false Как исправить эту ошибку Ошибка Неподдерживаемая версия major.minor 52.0 в студии Android? Ошибка после обновления сервисов Google Play до версии 11.0.0 Являются ли Android push уведомления надежными? Android пропустить активность по кнопке «Назад» Android Logcat: как я могу вывести сообщение журнала с типом TAB? Возможно ли использовать Google Voice Recognition (как пользовательскую услугу) в Google Glass?

Связанные группы в ExpandableListView

Есть ли стандартный способ привязать элемент группы к верхней части экрана во время прокрутки элементов группы. Я видел аналогичные примеры с ListView. Какие интерфейсы следует реализовать или какие методы переопределить?

Solutions Collecting From Web of "Связанные группы в ExpandableListView"

Я нашел решение на основе Pinned Header ListView от Peter Kuterna и андроидного образца ExpandableList1.java. PinnedHeaderExpListView.java

package com.example; import com.example.ExpandableList.MyExpandableListAdapter; import android.content.Context; import android.graphics.Canvas; 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()); } } } 

ExpandableList.java

 package com.example; import android.app.Activity; import android.graphics.Color; 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(R.id.list); elv.setAdapter(mAdapter); mPinnedHeaderBackgroundColor = getResources().getColor(android.R.color.black); mPinnedHeaderTextColor = getResources().getColor(android.R.color.white); elv.setGroupIndicator(null); View h = LayoutInflater.from(this).inflate(R.layout.header, (ViewGroup) findViewById(R.id.root), 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, Color.red(mPinnedHeaderBackgroundColor), Color.green(mPinnedHeaderBackgroundColor), Color.blue(mPinnedHeaderBackgroundColor))); header.setTextColor(Color.argb(alpha, Color.red(mPinnedHeaderTextColor), Color.green(mPinnedHeaderTextColor), Color.blue(mPinnedHeaderTextColor))); } } 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 } } } 

main.xml

 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/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> 

header.xml

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

Он работает так, как ожидалось, кроме нажатия на заголовок. Мне жаль, что щелчок по заголовку будет равным щелчку по элементу группы, но событие даже не произойдет, и OnClickListener не получит контроль. Зачем ?

EDIT: нажатие на заголовок также работает, если вы добавите следующий фрагмент кода внутри onCreate() действия ExpandableList.java .

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

Я попытался реализовать решение Homo Incognito и столкнулся с той же проблемой, что и закрепленный заголовок, который не доступен для кликов.

Похоже, что виновник сам ListView потребляет все события кликов, не передавая их в наш расширенный заголовок. Таким образом, вы можете попробовать копаться в реализации обработки щелчка ExpandableListView, что довольно беспорядок, учитывая его наследование вплоть до AdapterView.

Вместо того, чтобы пытаться вытащить клик из списка ListView, я пытаюсь обойти эту проблему, моделируя щелчок заголовка из элемента списка под ним. Для этого вам нужно реализовать onChildClick чтобы сначала увидеть, находится ли позиция onChildClick элемента под закрепленным заголовком, если это так, тогда вы можете передать клик на истинный заголовок, если нет, то просто обработайте клик для этого Обычно.

В приведенном ниже примере, когда щелкнутый элемент находится под закрепленным заголовком, я просто прокрутил ListView к истинному заголовку, заменив, таким образом, «расширенный» закрепленный заголовок истинным заголовком, и пользователь может предпринять дальнейшие действия оттуда, например Сворачивание группы.

Обратите внимание, что такой режим юзабилити работает только в том случае, если у вас нет кликабельного представления для элементов заголовка, иначе вам придется делать все реле щелчка и сопоставление между закрепленным виртуальным заголовком и истинным заголовком самостоятельно с помощью onInterceptTouchEvent .

После кода Homo Incognito в PinnedHeaderExpListView.java добавьте следующий метод:

 public int getHeaderViewHeight(){ return mHeaderViewHeight; } 

В onCreate добавьте следующее:

 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; } }); 

В ответе Homo Incognito, детское представление в закрепленном виде головы не может щелкнуть и получить событие щелчка, но я нашел способ. Я поставил код по адресу: https://github.com/chenjishi/PinnedHeadListView

 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]; mRect.top = 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; } } } 

Так я обрабатываю событие click в прикрепленном представлении, переопределяет dispatchTouchEvent.

Введите описание изображения здесь

В адаптере:

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

В getchildview

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

В onChildClick

  adapter.setSelectedPosition(childPosition); adapter.notifyDataSetChanged(); v.setSelected(true);