自定义ViewGroup-消息滚动条

转载请注明出处

这里写图片描述

消息滚动条之所以是自定义ViewGroup(以下使用RotateMsgView)而不是自定义View,是因为不想将每个消息体(View)写死,而是想像ListView一样,内容又每个itemView提供,而RotateMsgView需要做的是将每个View按顺序布局好,并将这些itemView动起来。
这时则需要一个CustomAdapter,和ListView的Adapter的作用是类似的

public interface CustomAdapter {
    int getCount();

    Object getItem(int position);

    View getView(int position, ViewGroup parent);

    int getItemViewType(int position);

    int getViewTypeCount();
}

设置CustomAdapter的实现类后,将Adapter中每个View添加到RotateMsgView中,而后调用requestLayout(),请求重新调用measure()和layout()
既然借鉴了ListView的Adapter,那么在onMeasure的时候也需要借鉴AbsListView的setItemViewLayoutParams(),为了避免ItemView的根布局设置match_parent在某些时候无效的情况,如以下代码:

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        childHeightSum = 0;// 所有View的高度的总和
        for (int i = 0; i < getChildCount(); i++) {
            View child = this.getChildAt(i);
            setItemViewLayoutParams(child);// 设置每个itemView的LayoutParams
            this.measureChild(child, widthMeasureSpec, heightMeasureSpec);
            int ch = child.getMeasuredHeight();
            childHeightSum += ch;
        }
    }

    /*===借鉴自AbsListView===*/
    private void setItemViewLayoutParams(View child) {
        final ViewGroup.LayoutParams vlp = child.getLayoutParams();
        LayoutParams lp;
        if (vlp == null) {
            lp = (LayoutParams) generateDefaultLayoutParams();
        } else if (!checkLayoutParams(vlp)) {
            lp = (LayoutParams) generateLayoutParams(vlp);
        } else {
            lp = (LayoutParams) vlp;
        }
        if (lp != vlp) {
            child.setLayoutParams(lp);
        }
    }

    @Override
    protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
        return new RotateMsgView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.WRAP_CONTENT);
    }

    @Override
    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
        return new LayoutParams(p);
    }

    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new RotateMsgView.LayoutParams(getContext(), attrs);
    }

    public static class LayoutParams extends ViewGroup.LayoutParams {
        public LayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);
        }

        public LayoutParams(int width, int height) {
            super(width, height);
        }

        public LayoutParams(ViewGroup.LayoutParams source) {
            super(source);
        }
    }
    /*===借鉴自AbsListView===*/

measure完毕后,则是将每个itemView放置在合适的位置,在onLayout中的代码如下:

// 此处的firstViewIndex是顶部可见的第一个View的索引值,这里将layout分为两部分,
// 可见View的索引到最后一个View的布局
for (int i = firstViewIndex; i < getChildCount(); i++) {
    View child = this.getChildAt(i);
    int ch = child.getMeasuredHeight();
    child.layout(0, startHeight, child.getMeasuredWidth(), startHeight + ch);
    startHeight += ch;
}
// 第1个View到可见View的布局
for (int i = 0; i < firstViewIndex; i++) {
    View child = this.getChildAt(i);
    int ch = child.getMeasuredHeight();
    child.layout(0, startHeight, child.getMeasuredWidth(), startHeight + ch);
    startHeight += ch;
}

以上就是从测量到布局所有的View已经完成了,那么就是如何将所有的View动起来了
在gif动图中,滑动完后会有一个暂停的效果,这里是通过ValueAnimator和Runnable完成的,通过postDelayed(),实现滞留效果,通过ValueAnimator来实现上下移动,具体实现看以下代码:

    // Runnable中主要就是确定第一个可见View的索引值,然后获取可见View的高度开启ValueAnimator
    private void initView(Context context, AttributeSet attrs, int defStyleAttr) {
        showAnimation.setDuration(mDelayMillis);
        hideAnimation.setDuration(mDelayMillis);
        animatorRunnable = new Runnable() {
            @Override
            public void run() {
                if (hasChangeFirstIndexWithMode) {
                    if (lastMode != mode) {
                        switch (mode) {
                            case Mode.MODE_ALPHA:
                                break;
                            case Mode.MODE_TRANSLATION_UP:
                                firstViewIndex++;
                                break;
                            case Mode.MODE_TRANSLATION_DOWN:
                                firstViewIndex--;
                                break;
                        }
                        if (firstViewIndex >= getChildCount()) {
                            firstViewIndex = 0;
                        }
                        if (firstViewIndex < 0) {
                            firstViewIndex = getChildCount() - 1;
                        }
                    }
                }
                switch (mode) {
                    case Mode.MODE_ALPHA:
                        startAnimator(1, 0, duration, mDelayMillis);
                        break;
                    case Mode.MODE_TRANSLATION_UP:
                        startAnimator(0, getChildAt(firstViewIndex).getMeasuredHeight(), duration, mDelayMillis);
                        break;
                    case Mode.MODE_TRANSLATION_DOWN:
                        startAnimator(getChildAt(firstViewIndex).getMeasuredHeight(), 0, duration, mDelayMillis);
                        break;
                }
                lastMode = mode;
            }
        };
    }
//==========================================================================
    // 主要就是在ValueAnimator开启的过程中不断的调用requestLayout(),
    // 而onLayout中就是结合animatedValue做相应的计算,将itemView放在对应的位置
    // 在ValueAnimator运行结束后,做个判断处理,修正firstViewIndex,并再次调用postDelayed(),如此形成一个循环
    private void startAnimator(float start, float end, long duration, long delayMillis) {
        if (animator == null) {
            animator = ValueAnimator.ofFloat(start, end);
            animator.setDuration(duration);
            animator.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationStart(Animator animation) {

                }

                @Override
                public void onAnimationEnd(Animator animation) {
                    super.onAnimationEnd(animation);
                    //初始化动画执行前的布局
                    switch (mode) {
                        case Mode.MODE_ALPHA:
                        case Mode.MODE_TRANSLATION_UP:
                            if (lastMode == mode) {
                                firstViewIndex++;
                            }
                            break;
                        case Mode.MODE_TRANSLATION_DOWN:
                            if (lastMode == mode) {
                                firstViewIndex--;
                            }
                            break;
                    }
                    if (firstViewIndex >= getChildCount()) {
                        firstViewIndex = 0;
                    }
                    if (firstViewIndex < 0) {
                        firstViewIndex = getChildCount() - 1;
                    }
                    postDelayed(animatorRunnable, mDelayMillis);
                    requestLayout();
                    Log.v("shan", "=====================================");
                }
            });
            animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    animatedValue = (Float) animation.getAnimatedValue();
                    Log.v("shan", "animatedValue:" + animatedValue);
                    requestLayout();
                }
            });
            animator.start();
        } else {//动画取消后再运行,会重置一切animator的参数
            if (!animator.isRunning()) {
                if (null == propertyValuesHolder) {
                    propertyValuesHolder = PropertyValuesHolder.ofFloat("y", start, end);
                } else {
                    propertyValuesHolder.setFloatValues(start, end);
                }
                animator.setValues(propertyValuesHolder);
                animator.setDuration(duration);
                animator.start();
            }
        }
    }

至此RotateMsgView大致结束了,还有个滞留的时候的渐隐渐显的效果是无关紧要的(RotateMsgView的高度一旦设定小于所有itemView的高度总和),其次就是一些细节处理了,具体看源码

https://github.com/cqf-hn/RotateMsgView


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值