Вопрос по android – Закрепленные группы в ExpandableListView

9

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

Ваш Ответ

5   ответов
3

блемой, что закрепленный заголовок не активен.

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

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



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

Note that such usability flow works only if you don't have any clickable view on the header items, otherwise you will have to do all the click relay and mapping between the pinned virtual header and the true header yourself with 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;
        }
    });
0

У меня действительно простое решение с использованием AXML и C #. Группа никогда не будет двигаться, и дети будут расширяться и прокручиваться.

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

This solution even works inside scrollviews, viewpagers etc...

  1. In your view, hard code your group view at the top of your ExpandableListView, with LinearLayout or whatever you need (this will never move ;-) ) and set the visibility property of your ExpandableListView to Gone.

    <LinearLayout
        android:id="@+id/llExpandableNotes"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginRight="10dp"
        android:layout_marginLeft="10dp"
        android:paddingTop="6dp"
        android:paddingBottom="6dp"
        android:background="@color/colorAliceBlue">
        <TextView
            android:id="@+id/txtExapndableNotes"
            android:layout_height="wrap_content"
            android:layout_width="wrap_content"
            android:layout_weight="1"
            android:layout_gravity="center|left"
            android:textSize="16dp"
            android:textColor="@color/colorDodgerBlue" />
        <ImageView
            android:id="@+id/imgExpandable"
            android:layout_height="wrap_content"
            android:layout_width="wrap_content"
            android:layout_gravity="center"
            android:src="@drawable/icon_plus_black" />
    </LinearLayout>
    <ExpandableListView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/expandableListViewNotes"
        android:textColor="@color/colorDodgerBlue"
        android:layout_marginRight="10dp"
        android:layout_marginLeft="10dp"
        android:groupIndicator="@null"
        android:visibility="gone"/>
    
  2. Keep using your Adapter, but we have to hide the group view cause we hard coded it in the first step. Just set a LinearLayout with height property to 0dp

Adapter

    public override View GetGroupView(int groupPosition, bool isExpanded, View convertView, ViewGroup parent)
    {            
        var inflater = _context.GetSystemService(Context.LayoutInflaterService) as LayoutInflater;
        convertView = inflater.Inflate(Resource.Layout.group_visibility_gone, null);

        return convertView;
    }

AXML template

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="0dp" />
  1. The event that expand the list is fired by the group we just came to hide. So let's keep it always open : expandableListViewNotes.ExpandGroup(0);

  2. Let's create an event by ourslef on the hard coded group

    private void llExpandableNotes_Clicked(object sender, EventArgs e)
    {
        switch(expandableListViewNotes.Visibility)
        {
            case ViewStates.Gone:
                imgExpandable.SetImageResource(Resource.Drawable.icon_minus_red);
                txtExapndableNotes.SetTextColor(Android.Graphics.Color.Red);
                expandableListViewNotes.Visibility = ViewStates.Visible;
                break;
    
            case ViewStates.Visible:
                imgExpandable.SetImageResource(Resource.Drawable.icon_plus_black);
                txtExapndableNotes.SetTextColor(Android.Graphics.Color.Black);
                expandableListViewNotes.Visibility = ViewStates.Gone;
                break;
        }
    }
    

РЕЗЮМЕ :

  • We have an ExpandableListView with an invisible group view (height=0dp)
  • We keep it always opened with ExpandGroup method
  • We create a fake header at the top of the ExapandableListView
  • ExpandableListView Visibility property starts with Gone state
  • We create a Click event on the fake group that set visibility on expandableListView to Visible or Gone.

И это все! Наслаждайтесь !

0

в ответе 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.

enter image description here

-3

в адаптере:

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);
Error: User Rate Limit Exceeded Homo Incognito
15

Я нашел решение на основеЗакрепленный заголовок ListView Петра Кутерна и образец Android 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 : clicking on header also works if you add following piece of code inside onCreate() методExpandableList.java деятельность.

        elv.setOnGroupClickListener(new OnGroupClickListener() {

        @Override
        public boolean onGroupClick(ExpandableListView parent, View v,
                int groupPosition, long id) {
            return false;
        }
    });
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded

Похожие вопросы