android listview item点击展开

版权所有,禁止匿名转载;禁止商业使用。

最近做项目真是头疼呢?之前想用ListViewAnnotation来着,就是可以实现类似于android 通知栏滑动删除的效果。好像是一位大牛自己一个人写的吧。我在这里首先向他致敬。不过,话说回来,实现原理也是比较易于理解的,就是检测你的滑动距离以及速度,再作出判断,进行操作。具体大家参考下Google keep的两种列表模式下滑动删除的操作就理解类。
非常不幸的事,我再布局中用了fragment和biewpager,所以产生了手势冲突,我为此改写了library里的手势操作检测方法,最后勉强改出来的。但是!!!!效果实在惨不忍睹,只好作罢,最后,思来想去,我决定使用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 1Like getMeasuredHeightAndState(), but only returns the raw width component (that is the result is masked by MEASURED_SIZE_MASK).
ReturnsThe raw measured height of this view.public final int getHeight ()Added in API level 1Return the height of your view.
ReturnsThe 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截图,使用者可以自己进行扩充。
listviewexpand.png哈哈,效果还行吧。那个,图标略显呆萌,还请不要见笑(自己绘制的,苦逼程序员一只啊)。

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

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

 

好吧,就写到这里吧,有什么问题,请留言。

 

0 0