首页 > 代码库 > 自定义ListView实现中间项动态变大的效果(不是自定义Adapter)

自定义ListView实现中间项动态变大的效果(不是自定义Adapter)

为什么强调不是自定义Adapter,因为我这个自定义控件是来源与公司新做的项目,刚开始在百度上找了一圈,都说是自定义ListView ,点进去却是自定义Adaper,有的人就会说你是不是太较真了,自定义Adapter就基本可以实现各种效果了,何必要自定义Listview,今天我做的这个还确实不好用Adapter做,先上效果图,右边的动图来源于左边这个项目中的一个控件。

技术分享技术分享


因为我们的项目中,六个通道的检测过程要同时动态显示,这样位置就要合理调配,因为六个通道采用的布局比较相似,所以当时考虑了Fragment或者自定义控件,后来,因为下面的四个按钮要对应四个不同的页面,也就需要使用viewpager+fragment,fragment中是不支持再用fragment的,而且每个通道的检测步骤会根据检测的项目类型而有所不同,所以,就只能采用自定义控件了。

说到自定义控件,就先要确定用哪种,不管你们怎么看,反正我第一眼看上去和listview 接近,我就先考虑自定义Listview了,刚开始也想用自定义adapter,我们要实现的是,给定item个数算出item高度并设置,我就想到了在自定义Listview中的onLayout中得到自定义Listview的高度,然后公共方法将Listview高度暴露出去,然后在adapter的getView 中得到自定义Listview的高度并计算设置item高度,事实证明,我想多了,item的getView方法是在Listview的onLayout方法前面执行的,有图为证

技术分享

为了找这个错误浪费我好多时间,说出来都是泪啊

那么尺寸参数就只能在自定义Listview中搞定了,动态item状态也是要在自定义ListView中搞定。

那我们的步骤就分清楚了

1.自定义adapter,动态给ListView中定个数

2.自定义Listview,给item设置尺寸信息,并实现滚动过程逻辑

3.在MainActivity中将数据绑定到自定义ListView.

先给出LvAdapter 的代码

package diasia.cdc.com.simplelistview;

import android.animation.ValueAnimator;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;

/**
 * Created by lv on 2015/1/15.
 */
public class LvAdapter extends BaseAdapter {
    private String[] strs;
    private Context context;
    private boolean b = true;

    public LvAdapter(String[] atrs, Context context) {
        this.strs = atrs;
        this.context = context;
    }

    @Override
    public int getCount() {
        return strs.length;
    }

    @Override
    public Object getItem(int position) {
        return strs[position];
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHold viewHold;
        if (convertView == null) {
            viewHold = new ViewHold();
            convertView = LayoutInflater.from(context).inflate(R.layout.item_layout, parent, false);
            convertView.setTag(viewHold);
        } else {
            viewHold = (ViewHold) convertView.getTag();
        }
        viewHold.textView = (TextView) convertView.findViewById(R.id.textview);
        viewHold.textView.setText(strs[position]);
        viewHold.textView.setTextSize(25);
        if (position == 1) {
            startScaleTo(convertView, 0.6f, 1.0f);
        } else {
            startScaleTo(convertView, 1.0f, 0.6f);
        }
        return convertView;
    }

    public void startScaleTo(final View view, float start, float end) {
        ValueAnimator animator = ValueAnimator.ofFloat(start, end);
        animator.setDuration(500);
        animator.start();
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float value = http://www.mamicode.com/(float) animation.getAnimatedValue();>里面的一般设置,我都不用讲了,你们应该也写到不能乱熟的了,至于复用的问题,应该也难不倒。我就解释这一句

if (position == 1) {
            startScaleTo(convertView, 0.6f, 1.0f);
        } else {
            startScaleTo(convertView, 1.0f, 0.6f);
        }
这就是初次加载item内容的时候,将除了第二个外的所有item缩小(给人的错觉就是中间那个放大了)

再贴出R.layout.item_layout.xml中的代码,一并把item_background.xml也贴出

<?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="match_parent"
    android:padding="20dp">
    <TextView
        android:id="@+id/textview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:text="正在预温"
        android:background="@drawable/item_background"/>
</LinearLayout>
item_background.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <stroke
        android:width="2dp"
        android:color="@android:color/holo_orange_dark"
        />
    <corners
        android:radius="150dp"
        />
</shape>

这两个文件不用解释,比较简单,就是决定了每个item的外观

然后就是今天的主角LvListView登场了,代码一段一段的贴,保证贴全,先贴出一些全局变量,后面对照着看

package diasia.cdc.com.simplelistview;

import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.LinearLayout;
import android.widget.ListView;

/**
 * TODO: document your custom view class.
 */
public class LvListView extends ListView implements AbsListView.OnScrollListener {
    /**
     * 显示在屏幕上多少个item
     */
    private int showItemCount;
    /**
     * 每个item的高度
     */
    private int itemHeight;
    /**
     * 记录第一个显示的item
     */
    /**
     * 从第几行执行动画
     */
    private int scaleFlagIndex = 1;
    /**
     * 是否到了最后一行
     */
    private boolean lastFlag = false;

    /**
     * 步骤
     */
    private int step = 0;

    /**
     * 步骤个数
     */
    private int stepCount;

接下来贴构造方法:

    

    public LvListView(Context context) {
        this(context, null);
    }

    public LvListView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    /**
     * 构造方法中拿到自定义属性showViewCount,并设置滚动监听
     *
     * @param context
     * @param attrs
     * @param defStyleAttr
     */
    public LvListView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        //获取showViewCount
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.LvListView);
        showItemCount = typedArray.getInt(R.styleable.LvListView_showViewCount, 0);
        typedArray.recycle();

        //设置一个滚动监听
        setOnScrollListener(this);
    }
构造方法中,做了两件事情:得到自定义属性中,用户设置的要显示的item的个数,我们后面设置为3,顺便贴出自定义属性的文件

<resources>
    <declare-styleable name="LvListView">
        <attr name="showViewCount" format="integer" />
    </declare-styleable>
</resources>
接下来重写onLayout方法来得到LvListView的尺寸来设置item的尺寸大小

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        if (changed) {
            itemHeight = this.getHeight() / showItemCount;
            for (int i = 0; i < getChildCount(); i++) {
                LinearLayout layout = (LinearLayout) getChildAt(i);
                AbsListView.LayoutParams layoutParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, itemHeight);
                layout.setLayoutParams(layoutParams);
            }
            stepCount = getAdapter().getCount() - 2;
        }
    }
其中,这一段弄的我很费解

LinearLayout layout = (LinearLayout) getChildAt(i);
                AbsListView.LayoutParams layoutParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, itemHeight);
                layout.setLayoutParams(layoutParams);
但是也是困扰我好久,刚开始用的ViewGroup.LayoutParams,总是报java.lang.ClassCastException:android.widget.LinearLayout$LayoutParams cannot be cast toandroid.widget.AbsListView$LayoutParams  ,我用反射得到getChildAt(i)的类型,是一个AbsListView类型,我表示很费解,明明我上面item布局的外层是一个LinearLayout布局,原因我找到了,是因为我的adapter中LayoutInflater.from(context).inflate(R.layout.item_layout, parent,false)这一句,虽然后面带的false,让效果没有实现,但是布局上的关系已经存在了,因为LayoutInflater.from(context).inflate(R.layout.item_layout, null)写会报警告,在外国一个网站上看到有说这种写法不标准,会出现问题(ps:虽然不知道什么问题,但是未知的问题是最可怕的,我就不试了),所以就将这个布局参数类型改了,改成了AbsListView.LayoutParams,反正不报错了就好。接着stepCount = getAdapter().getCount() - 2;是给步骤个数赋值,为甚么要减去2呢,因为第一步骤和最后一步我们压根就不走,不理解请看前面和下面动态图。

技术分享

重写onTouchEvent

    /**
     * 禁用手指滑动
     *
     * @param ev
     * @return
     */
    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        return false;
    }
注释很详细,不改释

接下里,缩放动画

    /**
     * 缩放动画
     *
     * @param view
     * @param start
     * @param end
     */
    public void startScaleAnimator(final View view, float start, float end) {
        ValueAnimator animator = ValueAnimator.ofFloat(start, end);
        animator.setDuration(500);
        animator.start();
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float value = http://www.mamicode.com/(float) animation.getAnimatedValue();>这里说一下,顺便把adapter中的一并解释了,因为我们的item是一个不高但是很宽的控件,那么横向缩放和纵向缩放不能采用统一标准,横向缩放就用传过来的值,但是纵向缩放就把值计算了一下,通过0.4f + (0.6f * value)后,就将缩放的区间减小了, 请带入同样0-1之间的值进去算,这里强调一下,小数的后面一定要带f,小心数据丢失。
接下来,就是我们的下一步按钮调用的方法了,后面我们公司的功能是弄到其它代码中调用的,不是用按钮

    /**
     * 设置滚动到指定item
     */
    public void scrollToItem() {
        step++;
        if (step == stepCount) {
            step = 0;
            this.lastFlag = true;
            startScaleAnimator(getChildAt(scaleFlagIndex), 1.0f, 0.6f);
            this.smoothScrollToPositionFromTop(step, 0, 1000);
        } else {
            this.smoothScrollToPositionFromTop(step, 0, 500);
        }
    }
step++是将步骤后推一步,也就是下一步,if (step == stepCount)判断是否到了最后一步,如果到了没有到最后一步,调用smoothScrollToPositionFromTop滚动到指定item,第二个参数是从视图顶部所需的距离在<代码>位置</ code>的像素滚动时结束,这一句是不是没懂?别问我,我也没懂,反正我没用,我就设置为0,第三个参数是滚动时间,至于插值器什么的,完全不用关心。如果到了最后一步,设置步骤step为0,将lastFlag标志改为true,后面会用到,startScaleAnimator(getChildAt(scaleFlagIndex), 1.0f, 0.6f);是将最后放大了的那个item缩小, this.smoothScrollToPositionFromTop(step, 0, 1000);至于为什么要给1000毫秒,因为从最后一个滚动到最前面,滚动的item太多,时间太短就不好看了。

接下来就是LvListView中的重点了,实现AbsListView.OnScrollListener接口并实现其中的两个抽象方法

    @Override
         public void onScrollStateChanged(AbsListView view, int scrollState) {
        Log.e("onScrollStateChanged", "scrollState--" + scrollState);
        if (scrollState == OnScrollListener.SCROLL_STATE_FLING) {
            if (!lastFlag) {
                startScaleAnimator(view.getChildAt(scaleFlagIndex), 1.0f, 0.6f);
                startScaleAnimator(view.getChildAt(scaleFlagIndex + 1), 0.6f, 1.0f);
                scaleFlagIndex = 2;
            }
        } else if (scrollState == OnScrollListener.SCROLL_STATE_IDLE && lastFlag) {
            this.lastFlag = false;
            scaleFlagIndex = 1;
        }
    }

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        Log.e("onScroll", "firstVisibleItem--" + firstVisibleItem);
    }
前者就是在listview每次往下滚动一下的时候,动态设置item的动画效果的逻辑了,AbsListView view表示当前显示在屏幕上的Listview的item的容器,scrollState就是当前listview的状态,scrollState有三种状态,分别是空闲(滚动完成),滚动中,开始滚动,
<span style="white-space:pre">	</span>public static int SCROLL_STATE_IDLE = 0;
        public static int SCROLL_STATE_TOUCH_SCROLL = 1;
        public static int SCROLL_STATE_FLING = 2;
我们点下一步的时候,动画肯定是要在滚动开始的时候同步播放的,所以if (scrollState == OnScrollListener.SCROLL_STATE_FLING)就是在滚动中设置滚动的时候同时播放动画面让第二个item缩小,点的是下一步,第一个item会消失,第二个会到第一个的位置,所以这么弄,i因为f (lastFlag)的时候,动画效果是在scrollToItem设置的,这里设置就重复了,所以套一个f (!lastFlag)条件,每次结束的时候都设置一下this.lastFlag = false;表示没有到最后一步,至于scaleFlagIndex = 1或者等于2的问题,这个问题很奇怪,回到item头部的时候,view.getChildAt(1)就会在第一个,所以这么设置一下,就会区别对待头部的第一次点击。

接下来说说onScroll,这个是在滚动中执行多次的方法,应该是我们用代码调用滚动的原因,我用log打印其中的值,不管怎么滚动,里面的四个值都太坚强,一动不动,这也是我为什么会采用属性动画这种性能相对比较低的方法来实现效果的原因。

最后,附上源码,因为要学习csdn课堂里面的知识,所以要了点分,有分的捧个场,没分的在联系我给你




















自定义ListView实现中间项动态变大的效果(不是自定义Adapter)