ListView实现隐藏,显示Item的部分View.以及部分问题

实现的功能

1.实现动态显示,隐藏ListView的Item部分View.效果如下图所示:
ListView实现隐藏,显示Item的部分View.以及部分问题_第1张图片

点击右侧的按钮,显示或者隐藏底部的操作布局,实现Item的部分布局的动态显示.

涉及到的知识点

1.inflate方法的不同参数的含义,以及作用.
2.View的绘制过程,主要包含onMeasure,onLayout,onDraw这三个方法.
3.自定义View.
4.如何控制Item的部分布局的显示,隐藏.

如何控制Item的部分布局的显示,隐藏

实现思路通过标记一个全局变量来记录当前是那个Item正在被操作(右侧按钮被点击),初始化的position是-1,表示所有的底部操作布局都是隐藏的.

/***
     * 标记操作下表的位置:从0开始
     */
    public int mShowBottomPosition = -1;

同时需要一个状态来标记当前被操作的Item的状态(隐藏或者显示).

/**
     * 表示当前position的状态
     */
    public int mCurrentPositionStatus = HIDE_BOTTOM;
    public static final int SHOW_BOTTOM = 1;
     public static final int HIDE_BOTTOM = 0;

为Item的右侧按钮添加点击事件,改变mShowBottomPosition的数值为当前点击的position.同时修改mCurrentPositionStatus的状态,如果被点击的Item的底部布局之前处于隐藏状态,则mCurrentPositionStatus = SHOW_BOTTOM,否则mCurrentPositionStatus = HIDE_BOTTOM.然后执行notifyDataSetChanged方法,重新绘制布局.
为右侧的按钮添加点击事件.代码如下:

holder.mGoImage.setOnClickListener(new OnClickListener()
            {
                @Override
                public void onClick(View v)
                {
                    showOrHideBottom(holder, position);
                }
            });
 /**
     * 显示或者隐藏底部控制器
     *
     * @param position 位置
     */
    private void showOrHideBottom(ViewHolder holder, int position)
    {
        LogUtils.i(TAG, "mShowBottomPosition111 :" + mShowBottomPosition + "   position :" + position);
        if (mShowBottomPosition != position)
        {
            mShowBottomPosition = position;
            mCurrentPositionStatus = SHOW_BOTTOM;
        } else
        {
            mShowBottomPosition = position;
            if (mCurrentPositionStatus == HIDE_BOTTOM)
            {
                mCurrentPositionStatus = SHOW_BOTTOM;
            } else
            {
                mCurrentPositionStatus = HIDE_BOTTOM;
            }
        }
        mAdapter.notifyDataSetChanged();
    }

然后在getView方法里面调用setBottomStatusByPosition()方法,这个方法才是真正的控制Item的底部布局的显示与隐藏的代码.代码如下:

/**
     * 设置底部操作的状态
     *
     * @param position
     */
    private void setBottomStatusByPosition(final View convertView, ViewHolder holder, final int position)
    {
        ((MeasureViewHeightLayout)convertView).setOnMeasureAfterInterface(null);
        LogUtils.i(TAG, "mShowBottomPosition2222 :" + mShowBottomPosition + "   position :" + position);
        if (mShowBottomPosition == position)
        {
            int status = mCurrentPositionStatus;
            LogUtils.i(TAG, "mShowBottomPosition22 status :" + status);
            if (status == SHOW_BOTTOM)
            {
                holder.bottom_action_bar.setVisibility(VISIBLE);
                holder.statusArrorImg.setImageResource(R.mipmap.close_bottom_operation_icon);
                if(mFileList.get(position).isDir())
                {
                    holder.op_shared.setVisibility(GONE);
                }
                else
                {
                    holder.op_shared.setVisibility(VISIBLE);
                }
                if(convertView != null)
                {
                    MeasureViewHeightLayout.onMeasureAfterInterface onMeasureAfterInterface
                            = new MeasureViewHeightLayout.onMeasureAfterInterface()
                    {
                        @Override
                        public void operation()
                        {
                            Log.i("coordinate","convertView to String :"+convertView.toString()+" x :"+convertView.getX()+"  Y :"+convertView.getY()
                                    +"   bottom :"+convertView.getBottom() + " top :"+convertView.getTop());
                            Rect rect = new Rect();
                            boolean visible = convertView.getLocalVisibleRect(rect);
                            LogUtils.i("coordinate", " visible :"+visible +" position :"+ position+" top :"+rect.top +"  left :"+rect.left
                                    +"  bottom :"+rect.bottom +"  right :"+ rect.right);
                            mList.getRefreshableView().smoothScrollBy(measureSmoothLength(rect.bottom),500);
                        }
                    };
                    ((MeasureViewHeightLayout)convertView).setOnMeasureAfterInterface(onMeasureAfterInterface);
                }
            } else
            {
                holder.bottom_action_bar.setVisibility(GONE);
                ((MeasureViewHeightLayout)convertView).setOnMeasureAfterInterface(null);
                holder.statusArrorImg.setImageResource(R.mipmap.pull_bottom_operation_icon);
            }
        } else
        {
            holder.bottom_action_bar.setVisibility(GONE);
            ((MeasureViewHeightLayout)convertView).setOnMeasureAfterInterface(null);
            holder.statusArrorImg.setImageResource(R.mipmap.pull_bottom_operation_icon);
        }
    }

上面的代码设计到一个自定义的View,下面会介绍它的用途,以及ListView的滚动操作.在控制ListView滚动的时候需要计算需要滚动的距离.代码如下:

  /****
     * /**
     * 70代表:每个Item的固定显示高度(dp)
     *68:代表需要显示和隐藏的那个部分View的高度(dp)
     * @param bottom
     * @return
     */

    private int measureSmoothLength(int bottom)
    {
        float density = mContext.getResources().getDisplayMetrics().density;
        int height = (int) ((70 + 68) * density);
        int smoothHeigth = height - bottom;
        return smoothHeigth;
    }

自定义View

1.如果仔细看看上面setBottomStatusByPosition方法,你一定看到了convertView被强制转化成MeasureViewHeightLayout这个类,这个类是我自定义的,那么你一定非常好奇,既然已经实现了上面的效果,为什么还要自定义View,多此一举呢,说到这儿,看个GIF,相信你就明白了.

当屏幕滑到最后一个item或者屏幕中显示的最后的item没有显示底部操作View,而你去点击右侧的按钮的时候,这个时候你点击右侧的展开按钮,是看不到底部的操作的部分的,这个时候,滑动ListView才可以看到底部的部分.针对这个问题,我们需要判断,我们要显示的那个Item是否完全显示,开始,我想到的是直接测量Item的高度,来判断是否完全显示,但是发现不好使,因为,当我们在setBottomStatusByPosition方法里面测量View的高度的时候,Item还没有测量完,这个时候,我们就需要自定义View.在View执行完毕onMeasure方法以后,回调回来,然后在通过getLocalVisibleRect的方法,获取Item的左上角,右下角坐标,然后得到Item的高度.然后根据View的可见高度,和我们期望的View的高度,得出该Item是否完全显示,用到的方法就是上面的measureSmoothLength().


/**

 */

public class MeasureViewHeightLayout extends LinearLayout
{
    private onMeasureAfterInterface onMeasureAfterInterface;
    public MeasureViewHeightLayout(Context context)
    {
        super(context);
    }

    public MeasureViewHeightLayout(Context context, @Nullable AttributeSet attrs)
    {
        super(context, attrs);
    }

    public MeasureViewHeightLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr)
    {
        super(context, attrs, defStyleAttr);
    }

    @TargetApi(21)
    public MeasureViewHeightLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)
    {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    @Override
    protected void onDraw(Canvas canvas)
    {
        LogUtils.i("coordinate","on draw ");
        super.onDraw(canvas);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        LogUtils.i("coordinate","on measure ");

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b)
    {
        LogUtils.i("coordinate","on layout ");
        if(onMeasureAfterInterface != null)
        {
            onMeasureAfterInterface.operation();
        }
        super.onLayout(changed, l, t, r, b);
    }

    public void setOnMeasureAfterInterface(onMeasureAfterInterface onMeasureAfterInterface)
    {
        this.onMeasureAfterInterface = onMeasureAfterInterface;
    }
    public interface onMeasureAfterInterface
    {
        void operation();
    }
}

View的绘制过程

measure是测量的意思,那么onMeasure()方法顾名思义就是用于测量视图的大小的。View系统的绘制流程会从ViewRoot的performTraversals()方法中开始,在其内部调用View的measure()方法。measure()方法接收两个参数,widthMeasureSpec和heightMeasureSpec,这两个值分别用于确定视图的宽度和高度的规格和大小。

MeasureSpec的值由specSize和specMode共同组成的,其中specSize记录的是大小,specMode记录的是规格。specMode一共有三种类型,如下所示:

  1. EXACTLY

表示父视图希望子视图的大小应该是由specSize的值来决定的,系统默认会按照这个规则来设置子视图的大小,开发人员当然也可以按照自己的意愿设置成任意的大小。

  1. AT_MOST

表示子视图最多只能是specSize中指定的大小,开发人员应该尽可能小得去设置这个视图,并且保证不会超过specSize。系统默认会按照这个规则来设置子视图的大小,开发人员当然也可以按照自己的意愿设置成任意的大小。

  1. UNSPECIFIED

表示开发人员可以将视图按照自己的意愿设置成任意的大小,没有任何限制。这种情况比较少见,不太会用到。
以将视这里不做过多介绍,网上的资料非常全,这里贴出几个不错的blog:
https://my.oschina.net/xwy/blog/203198
http://blog.csdn.net/qinjuning/article/details/7110211/

http://blog.csdn.net/guolin_blog/article/details/17045157

getLocalVisibleRect方法

Rect rect = new Rect();
boolean visible = convertView.getLocalVisibleRect(rect);

LogUtils.i("coordinate", " visible :"+visible +" position :"+ position+" top :"+rect.top +"  left :"+rect.left
                                    +"  bottom :"+rect.bottom +"  right :"+ rect.right);

coordinate通过日志可以看出来,这方法,只要是返回,View的左上角,右下角的坐标,这个坐标是相对于View本身而言的.关于View坐标的问题,这里面,也是贴出其他blog的地址,读者可以自行研究.这里没必要做出过多的拓展

http://blog.csdn.net/lvxiangan/article/details/19971509

http://www.cnblogs.com/ai-developers/p/4413585.html

注意事项

1.在getView的方法里面,会通过inflate方法来初始化convertView,但是inflate的三个参数确实大有用途的,
inflate()方法一般接收两个参数,第一个参数就是要加载的布局id,第二个参数是指给该布局的外部再嵌套一层父布局,如果不需要就直接传null。这样就成功成功创建了一个布局的实例,之后再将它添加到指定的位置就可以显示出来了。
第三个参数在这必须是false,否则返回的对象就不是MeasureViewHeightLayout对象了.

convertView = layoutInflater.inflate(R.layout.file_item, parent,false);

http://blog.csdn.net/guolin_blog/article/details/12921889

2.根据ListView的复用机制,需要在不要回调的地方设置回调为null,避免多次回调operation()方法,导致ListView多次执行smoothScrollBy方法,出现错误.

smoothScrollBy(heigth, time);//平滑height,在time内(毫秒单位).

你可能感兴趣的:(android)