在前面的自定义控件概述中已经跟大家分享了Android开发当中自定义控件的种类。今天跟大家分享一个非主流的组合控件。
我们在开发当中,难免需要在不同的场合中重复使用一些控件的组合。而Java的最高目标呢,是消灭所有重复代码。这个时候怎么办呢?办法之一就是创建一个囊括逻辑和布局的视图,以便可以重复使用而不用在不同的场合中写重复的代码。代码复用的同时我们还把逻辑包装到了控件内部,做到更好的解耦。比如我们App页面中的顶栏等等。
今天呢,跟大家分享一个我前一阵子在项目中遇到的实例。先看下效果图:
自己写一个类继承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回调中调用,保证在控件加载完成之后再设置参数。
到这里这个控件就基本完成了。其实还有很多可以优化的地方,比如把一些属性抽离出来,写成自定义属性,还有下标根据传入数组动态生成等等,有兴趣的朋友可以交流一下。源码地址。