Androd自定义控件(三)飞翔的小火箭

在前面的自定义控件概述中已经跟大家分享了Android开发当中自定义控件的种类。今天跟大家分享一个非主流的组合控件。
我们在开发当中,难免需要在不同的场合中重复使用一些控件的组合。而Java的最高目标呢,是消灭所有重复代码。这个时候怎么办呢?办法之一就是创建一个囊括逻辑和布局的视图,以便可以重复使用而不用在不同的场合中写重复的代码。代码复用的同时我们还把逻辑包装到了控件内部,做到更好的解耦。比如我们App页面中的顶栏等等。
今天呢,跟大家分享一个我前一阵子在项目中遇到的实例。先看下效果图:

需求:

  1. 该控件可以左右滑动。
  2. 底部积分是一个等差数列,可以自己定义。积分初始为半透明,小红旗下方显示设定的最大值。小火箭会飞到当前用户对应的积分位置,用户得到的积分在小火箭动画之后会显示为白色,同时当前积分位置出现一条标识线。
  3. 动画开始的时候小火箭会从0开始移动,直到当前积分位置,在移动过程中小火箭会有一个喷射火焰的效果。
  4. 背景会随着火箭的移动而移动,当动画结束的时候,保证小火箭在屏幕中心。

实现方式:

自己写一个类继承HorizontalScrollView,HorizontalScrollView会帮我们处理左右滑动的事件,否则还要重写ontouchEvent自己处理滑动。然后加载一个布局文件,给小火箭加一个帧动画和位移属性动画,实现小火箭的移动和喷火动画。同时自定义一个动画,来处理控件本身的滑动。

需要的技能点:

1.Android的view动画和属性动画,以及简单的自定义动画。
2.view的绘制流程,详情参照Androd自定义控件(一)概述 。
3.Activity中view的加载机制。
4.Android中dp,px等单位的概念。
5.用代码创建控件。
6.LayoutParams的使用方法。

具体实现:

初始化,在这里我们让一个参数的构造方法调用两个参数的构造方法,两个参数的构造方法调用三个参数的构造方法,把初始化的方法放到三个参数的构造方法当中。在初始化方法中加载布局文件。

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

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

    public PointView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        LayoutInflater.from(context).inflate(R.layout.point_view, this);
        initView();

        this.context = context;

        bottomLeftMargin = UIUtil.dip2px(context, 65);
    }

    private void initView() {
        //顶部内容区域
        content = (FrameLayout) findViewById(R.id.point_content);
        rocket = (ImageView) findViewById(R.id.point_rocket);

        //底部标注
        one = (TextView) findViewById(R.id.point_one);
        two = (TextView) findViewById(R.id.point_two);
        three = (TextView) findViewById(R.id.point_three);
        four = (TextView) findViewById(R.id.point_four);
        five = (TextView) findViewById(R.id.point_five);
        six = (TextView) findViewById(R.id.point_six);
        seven = (TextView) findViewById(R.id.point_seven);
        pointMax = (TextView) findViewById(R.id.point_max);

        mark = (LinearLayout) findViewById(R.id.point_mark);
        bottom = (FrameLayout) findViewById(R.id.point_bottom);
    }

布局文件

<?xml version="1.0" encoding="utf-8"?>
<HorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:scrollbars="none">

    <LinearLayout  android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@mipmap/point_view_bg" android:orientation="vertical">

        <!-- 内容区域 -->
        <FrameLayout  android:id="@+id/point_content" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1">

            <ImageView  android:id="@+id/point_rocket" android:layout_width="80dp" android:layout_height="wrap_content" android:layout_marginLeft="15dp" android:layout_marginTop="85dp" android:src="@mipmap/rocket_four" />

        </FrameLayout>

        <!-- 底部标注 -->
        <FrameLayout  android:id="@+id/point_bottom" android:layout_width="match_parent" android:layout_height="57dp">

            <LinearLayout  android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal">

                <TextView  android:layout_width="30dp" android:layout_height="wrap_content" android:layout_marginLeft="10dp" android:layout_marginTop="7dp" android:text="积分" android:textColor="#fff" android:textSize="14sp" />

                <LinearLayout  android:id="@+id/point_mark" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1">

                    <LinearLayout  android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:orientation="vertical">

                        <LinearLayout  android:layout_width="50dp" android:layout_height="match_parent" android:gravity="center_horizontal" android:orientation="vertical">

                            <ImageView  android:layout_width="1px" android:layout_height="5dp" android:background="@color/light_red" />

                            <TextView  android:id="@+id/point_one" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="3dp" android:text="0" android:textColor="@color/white" android:textSize="12sp" />
                        </LinearLayout>

                    </LinearLayout>

                    <LinearLayout  android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:orientation="vertical">

                        <LinearLayout  android:layout_width="50dp" android:layout_height="match_parent" android:gravity="center_horizontal" android:orientation="vertical">

                            <ImageView  android:layout_width="1px" android:layout_height="5dp" android:background="@color/light_red" />

                            <TextView  android:id="@+id/point_two" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="3dp" android:text="300" android:textColor="@color/zhuce" android:textSize="12sp" />
                        </LinearLayout>

                    </LinearLayout>

                    <LinearLayout  android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:orientation="vertical">

                        <LinearLayout  android:layout_width="50dp" android:layout_height="match_parent" android:gravity="center_horizontal" android:orientation="vertical">

                            <ImageView  android:layout_width="1px" android:layout_height="5dp" android:background="@color/light_red" />

                            <TextView  android:id="@+id/point_three" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="3dp" android:text="600" android:textColor="@color/zhuce" android:textSize="12sp" />
                        </LinearLayout>

                    </LinearLayout>

                    <LinearLayout  android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:orientation="vertical">

                        <LinearLayout  android:layout_width="50dp" android:layout_height="match_parent" android:gravity="center_horizontal" android:orientation="vertical">

                            <ImageView  android:layout_width="1px" android:layout_height="5dp" android:background="@color/light_red" />

                            <TextView  android:id="@+id/point_four" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="3dp" android:text="900" android:textColor="@color/zhuce" android:textSize="12sp" />
                        </LinearLayout>

                    </LinearLayout>

                    <LinearLayout  android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:orientation="vertical">

                        <LinearLayout  android:layout_width="50dp" android:layout_height="match_parent" android:gravity="center_horizontal" android:orientation="vertical">

                            <ImageView  android:layout_width="1px" android:layout_height="5dp" android:background="@color/light_red" />

                            <TextView  android:id="@+id/point_five" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="3dp" android:text="1200" android:textColor="@color/zhuce" android:textSize="12sp" />
                        </LinearLayout>

                    </LinearLayout>

                    <LinearLayout  android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:orientation="vertical">

                        <LinearLayout  android:layout_width="50dp" android:layout_height="match_parent" android:gravity="center_horizontal" android:orientation="vertical">

                            <ImageView  android:layout_width="1px" android:layout_height="5dp" android:background="@color/light_red" />

                            <TextView  android:id="@+id/point_six" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="3dp" android:text="1500" android:textColor="@color/zhuce" android:textSize="12sp" />
                        </LinearLayout>

                    </LinearLayout>

                    <LinearLayout  android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:orientation="vertical">

                        <LinearLayout  android:layout_width="50dp" android:layout_height="match_parent" android:gravity="center_horizontal" android:orientation="vertical">

                            <ImageView  android:layout_width="1px" android:layout_height="5dp" android:background="@color/light_red" />

                            <TextView  android:id="@+id/point_seven" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="3dp" android:text="1800" android:textColor="@color/zhuce" android:textSize="12sp" />
                        </LinearLayout>

                    </LinearLayout>

                </LinearLayout>

                <TextView  android:id="@+id/point_max" android:layout_width="58dp" android:layout_height="wrap_content" android:layout_marginLeft="10dp" android:layout_marginTop="7dp" android:gravity="center" android:textColor="@color/zhuce" android:textSize="12sp" />

                <View  android:layout_width="33dp" android:layout_height="match_parent" />
            </LinearLayout>
        </FrameLayout>

    </LinearLayout>

</HorizontalScrollView>

然后在onlayout方法中拿到我们需要的底部标注的长度,用来计算小火箭和view动画的位移。

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        markLength = mark.getMeasuredWidth();
// L.e(TAG, "markLength---" + markLength);
    }

设置显示标注线

/** * 设置当前分数 * * @param point */
    public void setCurrentPoint(int point) {
        int location;
        if (point < MAX_POINT) {
            location = (int) ((point / MAX_POINT) * markLength + bottomLeftMargin);//算出当前分数显示位置的偏移量
        } else {
            location = markLength + bottomLeftMargin + UIUtil.dip2px(context, 12);
        }

        //标注当前位置,
        ImageView line = new ImageView(context);
        line.setImageDrawable(getResources().getDrawable(R.color.point_line));
        bottom.addView(line);
        LayoutParams linePa = (LayoutParams) line.getLayoutParams();
        linePa.leftMargin = location;
        linePa.width = UIUtil.dip2px(context, 1);
        linePa.height = UIUtil.dip2px(context, 58);
        line.setLayoutParams(linePa);

// L.e(TAG, "location---" + location + ";bottomLeftMargin---" + bottomLeftMargin);
    }

火箭的动画

//火箭平移动画
        ObjectAnimator rocketAni = ObjectAnimator.ofFloat(rocket, "translationX", rocketX);
        DecelerateInterpolator interpolator = new DecelerateInterpolator();
        rocketAni.setInterpolator(interpolator);
        rocketAni.setDuration(DEFAULT_DURATION);
        rocketAni.start();

        //火箭切换动画
        rocket.setImageResource(R.drawable.rocket_frame);
        final AnimationDrawable animationDrawable = (AnimationDrawable) rocket.getDrawable();
        animationDrawable.start();

        rocketAni.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                //停止帧动画
                animationDrawable.stop();
                rocket.setImageResource(R.mipmap.rocket_three);
                //设置当前积分标注线
                setCurrentPoint(point);
                //设置已经到达积分为白色
                setMarkColor(point, 300);
            }
        });

因为scroller自带的滚动插值器与火箭动画插值器不同步,所以使用自定义动画实现控件的平滑滚动

/** * 自定义动画,控制scrollview滚动 */
    public class ViewAnimation extends Animation {

        private int viewX;

        public ViewAnimation(int viewX) {
            this.viewX = viewX;
        }

        @Override
        protected void applyTransformation(float interpolatedTime, Transformation t) {
            super.applyTransformation(interpolatedTime, t);
            smoothScrollTo((int) (viewX * interpolatedTime), 0);
        }
    }
    //view滚动动画
        /** * scroller自带的滚动插值器与火箭动画插值器不同步,所以使用自定义动画实现平滑滚动 */
        ViewAnimation viewAnimation = new ViewAnimation(finalViewX);
        viewAnimation.setDuration(DEFAULT_DURATION);
        viewAnimation.setInterpolator(interpolator);
        this.setAnimation(viewAnimation);
        viewAnimation.start();

调用

@Override
    public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);
        mPoinView.setMaxPoint(2500);
        mPoinView.startAni(600);
    }

这里我们在onWindowFocusChanged回调中调用,保证在控件加载完成之后再设置参数。

到这里这个控件就基本完成了。其实还有很多可以优化的地方,比如把一些属性抽离出来,写成自定义属性,还有下标根据传入数组动态生成等等,有兴趣的朋友可以交流一下。源码地址。

你可能感兴趣的:(android,布局,控件)