android listview item点击展开

最近做项目真是头疼呢?之前想用ListViewAnnotation来着,就是可以实现类似于android 通知栏
滑动删除的效果。好像是一位大牛自己一个人写的吧。我在这里首先向他致敬。不过,话说回来,实现
原理也是比较易于理解的,就是检测你的滑动距离以及速度,再作出判断,进行操作。具体大家参考
下Google keep的两种列表模式下滑动删除的操作就理解类。

非常不幸的事,我再布局中用了fragment和biewpager,所以产生了手势冲突,我为此改写了librar
y里的手势操作检测方法,最后勉强改出来的。但是!!!!效果实在惨不忍睹,只好作罢,最后,思来
想去,我决定使用listview item点击展开来实现我想要的效果。好吧,废话说了很多,终于要进入
正题了。

首先补充三点知识:

第一点:

getLayoutParams()方法和setLayoutParams()用法
首先,我们利用getLayoutParams()来获得指定控件的LayoutParams。例如:
LinearLayout.LayoutParams lp = (RelativeLayout.LayoutParams) deletButton.getLayoutParams();

然后,我们可以为deleteButton设置layoutparams属性,例如:
    lp.width = btnWidth;
    lp.leftMargin = 10;
最后,我们将设置好的layoutparams属性设置给指定的控件:        deletButton.setLayoutParams(lp);

第二点:

public final int getMeasuredHeight ()
Added in API level 1
Like getMeasuredHeightAndState(), but only returns the raw width component (that is the result is masked by MEASURED_SIZE_MASK).

Returns
The raw measured height of this view.
public final int getHeight ()
Added in API level 1
Return the height of your view.

Returns
The height of your view, in pixels.
getMeasuredHeight()返回的是原始测量高度,与屏幕无关,getHeight()返回的是在屏幕上显示的高度。实际上在当屏幕可以包裹内容的时候,他们的值是相等的,只有当view超出屏幕后,才能看出他们的区别。当超出屏幕后,getMeasuredHeight()等于getHeight()加上屏幕之外没有显示的高度。

第三点:

MeasureSpec的使用。MeasureSpec一般出现在自定义View中,因为在自定义 view中我们经常需要重写该方法,由它来指定自定义控件在屏幕上的大小。
protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec)
onMeasure传入的两个参数是由上一层控件传入的大小,有多种情况,重写该方法时需要对计算控件的实际大小,然后调用setMeasuredDimension(int, int)设置实际大小。

onMeasure传入的widthMeasureSpec和heightMeasureSpec不是一般的尺寸数值,而是将模式和尺寸组合在一起的数值。我们需要通过int mode = MeasureSpec.getMode(widthMeasureSpec)得到模式,用int size = MeasureSpec.getSize(widthMeasureSpec)得到尺寸。

mode共有三种情况,取值分别为MeasureSpec.UNSPECIFIED, MeasureSpec.EXACTLY, MeasureSpec.AT_MOST。

MeasureSpec.EXACTLY是精确尺寸,当我们将控件的layout_width或layout_height指定为具体数值时如andorid:layout_width="50dip",或者为FILL_PARENT是,都是控件大小已经确定的情况,都是精确尺寸。

MeasureSpec.AT_MOST是最大尺寸,当控件的layout_width或layout_height指定为WRAP_CONTENT时,控件大小一般随着控件的子空间或内容进行变化,此时控件尺寸只要不超过父控件允许的最大尺寸即可。因此,此时的mode是AT_MOST,size给出了父控件允许的最大尺寸。

MeasureSpec.UNSPECIFIED是未指定尺寸,这种情况不多,一般都是父控件是AdapterView,通过measure方法传入的模式。
然后,我们可以调用MeasureSpec.makeMeasureSpec(int size, int mode)来进行设置。

好,现在先上一张demo截图,使用者可以自己进行扩充。
Alt text
哈哈,效果还行吧。那个,图标略显呆萌,还请不要见笑(自己绘制的,苦逼程序员一只啊)。

好,接下来,我们来看一下动效的实现。当然,先上源码。

package wenyue.expandlistview.me;

import android.util.Log;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.Transformation;
import android.widget.LinearLayout.LayoutParams;

    /**
     * animation
     * 2014年十一月十八日
     * 苍狼问月
     * canglangwenyue.github.io
     * @author canglangwenyue
     *
     */
public class ViewExpandAnimation extends Animation {

private View mAnimationView = null;
private LayoutParams mViewLayoutParams = null;
private int mStart = 0;
private int mEnd = 0;

public ViewExpandAnimation(View view){
    animationSettings(view, 500);
}

public ViewExpandAnimation(View view, int duration){
    animationSettings(view, duration);
}

private void animationSettings(View view, int duration){
    setDuration(duration);
    mAnimationView = view;
    mViewLayoutParams = (LayoutParams) view.getLayoutParams();
    mStart = mViewLayoutParams.bottomMargin;
    mEnd = (mStart == 0 ? (0 - view.getHeight()) : 0);
    view.setVisibility(View.VISIBLE);
}

@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
    super.applyTransformation(interpolatedTime, t);

    if(interpolatedTime < 1.0f){
        mViewLayoutParams.bottomMargin = mStart + (int) ((mEnd - mStart) * interpolatedTime);
        // invalidate
        mAnimationView.requestLayout();
    }else{
        mViewLayoutParams.bottomMargin = mEnd;
        mAnimationView.requestLayout();
        if(mEnd != 0){
            mAnimationView.setVisibility(View.GONE);
        }
    }
    Log.i("hehe", "interpolatedTime = " + interpolatedTime + " , bottomMargin" + 
            mViewLayoutParams.bottomMargin);
}

}

动效实现的代码不多,setDuration(duration)设置动效的持续时间。之后调用RelitaveLayout.bottomMargin;对于该属性developer文档解释为The bottom margin in pixels of the child.
mAnimationView.requestLayout();该方法内部会调用Object.layout(...)方法,来重会布局。

至于,listview adapter的重写,就比较简单了。所以,不再贴出全部源码。只说一下需要注意的地方。
首先,你需要在getView(…)中加载自定义listview 的item布局文件:

convertView = myInflater.inflate(R.layout.expand_item, null);

接下来,贴出相应布局文件代码:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="5dip" >

<RelativeLayout
    android:id="@+id/header"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" >

    <LinearLayout
        android:id="@+id/appInfo"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="5dip"
        android:orientation="vertical" >

        <TextView
            android:id="@+id/tvName"
            android:layout_width="match_parent"
            android:layout_height="45dp"
            android:gravity="center|left"
            android:text="name"
            android:textColor="#000000"
            android:layout_marginLeft="20.0dip"
            android:textSize="16sp" />
    </LinearLayout>
</RelativeLayout>

<RelativeLayout
    android:id="@+id/footer"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="#ffffff" >


    <Button
        android:id="@+id/btnOpen"
        android:layout_width="100dip"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_marginTop="6dip"
        android:focusable="false"
        android:textColor="#000000"
        android:background="@drawable/editbutton"
        android:textSize="16sp" />

    <Button
        android:id="@+id/btnView"
        android:layout_width="100dip"
        android:layout_height="wrap_content"
        android:layout_marginTop="6dip"
        android:layout_toRightOf="@id/btnOpen"
        android:focusable="false"
        android:background="@drawable/start"
        android:textColor="#000000"
        android:textSize="16sp" />

    <Button
        android:id="@+id/btnWarning"
        android:layout_width="100dip"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_marginTop="6dip"
        android:focusable="false"
        android:background="@drawable/tomatodeletebutton"
        android:textColor="#000000"
        android:textSize="16sp" />
</RelativeLayout>
</LinearLayout>

我设置的是默认在activity onStart时嵌套布局中的第二个RelativeLayout布局文件的内容是被隐藏的。
在BaseAdapter中的设置如下:

RelativeLayout footer = (RelativeLayout) convertView
                .findViewById(R.id.footer);
        int widthSpec = MeasureSpec.makeMeasureSpec(
                (int) (mLcdWidth - 10 * mDensity), MeasureSpec.EXACTLY);
        footer.measure(widthSpec, 0);
        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) footer
                .getLayoutParams();
        params.bottomMargin = -footer.getMeasuredHeight();
        footer.setVisibility(View.GONE);

然后,是在onCreate方法中设置setOnItemClickListener方法,在其中设置点击item后,开始动效,再次点击则隐藏,且同一时间只有一个item可以展开(因为个人认为用户不可能在同一时间操作两个 item的隐藏操作),代码如下:

if (mLastTouchTag != null) {
                View temp = arg0.findViewWithTag(mLastTouchTag);
                if (temp != null) {
                    View footTemp = temp.findViewById(R.id.footer);
                    if (footTemp != null
                            && (footTemp.getVisibility() != View.GONE)) {
                        footTemp.startAnimation(new ViewExpandAnimation(
                                footTemp));
                    }
                }
            }
            mLastTouchTag = (ViewHolder) v.getTag();
            // onion555 end
            View footer = v.findViewById(R.id.footer);
            footer.startAnimation(new ViewExpandAnimation(footer));

好吧,就写到这里吧,有什么问题,请留言。同样,在最后附上源码下载地址。
点此下载