【问题标题】:Pinned groups in ExpandableListViewExpandableListView 中的固定组
【发布时间】:2018-08-24 20:51:20
【问题描述】:

在滚动组项目时,是否有一种标准方法可以将组项目固定到屏幕顶部。我在 ListView 中看到了类似的示例。我应该实现哪些接口或覆盖哪些方法?

【问题讨论】:

    标签: android expandablelistview


    【解决方案1】:

    我找到了基于 Peter Kuterna 的 Pinned Header 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 没有获得控制权。为什么?

    编辑: 如果您在ExpandableList.java 活动的onCreate() 方法中添加以下代码,则单击标题也有效。

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

    【讨论】:

    • 你从哪里得到你的部分?因为 group 是包含子项的列表项。我看不到您设置标题和部分的任何地方。您能否添加屏幕截图,以便我知道该代码的预期内容?你如何设置部分?在 configurePinnedHeader 中,它创建了一个标题,但它只被调用一次,因为我不知道如何创建标题下的部分。我已经阅读了很多次答案,但我似乎无法弄清楚。请帮忙?
    • 这非常好...谢谢 :) 通过我们可以使组标题始终可见...在当前形式下,如果项目数,则仅固定当前活动标题与之相关联的标题很大,并且其他标题被推出查看区域
    • 嘿@Homo 有什么方法可以添加动画来展开/折叠操作吗?
    • 固定标题内的文本视图不完全可见
    • 这对我来说是个例外java.lang.NullPointerException: Attempt to read from field 'int android.view.ViewGroup$LayoutParams.width' on a null object reference at com.example.view.PinnedHeaderExpListView.onMeasure(PinnedHeaderExpListView.java:93) 任何建议!!
    【解决方案2】:

    我尝试实现了 Homo Incognito 的解决方案,但遇到了同样的问题,即 pinned header 不可点击。

    罪魁祸首似乎是 ListView 本身消耗了所有点击事件,因此没有将任何事件传递给我们的“增强”标题视图。所以你可以尝试挖掘 ExpandableListView 的点击处理实现,考虑到它一直到 AdapterView 的继承,这真是一团糟。

    我没有尝试从 ListView 中劫持点击,而是尝试通过模拟其下方列表项的标题点击来绕过此类问题。为此,您需要实现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;
            }
        });
    

    【讨论】:

      【解决方案3】:

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

      这是我在固定视图中处理点击事件的方式,覆盖 dispatchTouchEvent。

      【讨论】:

        【解决方案4】:

        我有一个使用 AXML 和 C# 的非常简单的解决方案。该组永远不会移动,子项将可以展开和滚动。

        我将在这里向您展示列表中只有一个组,开始时处于关闭状态的情况。

        此解决方案甚至适用于滚动视图、viewpagers 等...

        1. 在您的视图中,使用 LinearLayout 或您需要的任何内容在 ExpandableListView 顶部硬编码您的组视图(这永远不会移动 ;-))并将 ExpandableListView 的可见性属性设置为 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. 继续使用您的适配器,但我们必须隐藏组视图,因为我们在第一步中对其进行了硬编码。 只需将具有高度属性的 LinearLayout 设置为 0dp

        适配器

            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 模板

            <?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. 扩展列表的事件被我们刚刚隐藏的组触发。 因此,让我们始终保持打开状态: expandableListViewNotes.ExpandGroup(0);

        2. 让我们自己在硬编码组上创建一个事件

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

        总结:

        • 我们有一个带有不可见组视图的 ExpandableListView (height=0dp)
        • 我们始终使用 ExpandGroup 方法打开它
        • 我们在 ExapandableListView 的顶部创建了一个假标题
        • ExpandableListView Visibility 属性以 Gone 状态开始
        • 我们在假组上创建一个 Click 事件,将 expandableListView 上的可见性设置为 Visible 或 Gone。

        仅此而已!享受吧!

        【讨论】:

          【解决方案5】:

          在适配器中:

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

          【讨论】:

          • 这根本不是我想要的
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2015-05-18
          • 2012-03-17
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-11-28
          • 1970-01-01
          相关资源
          最近更新 更多