在前面的自定義控件概述中已經跟大家分享了Android開發當中自定義控件的種類。今天跟大家分享一個非主流的組合控件。
我們在開發當中,難免需要在不同的場合中重複使用一些控件的組合。而Java的最高目標呢,是消滅所有重複代碼。這個時候怎麼辦呢?辦法之一就是創建一個囊括邏輯和佈局的視圖,以便可以重複使用而不用在不同的場合中寫重複的代碼。代碼複用的同時我們還把邏輯包裝到了控件內部,做到更好的解耦。比如我們App頁面中的頂欄等等。
今天呢,跟大家分享一個我前一陣子在項目中遇到的實例。先看下效果圖:
需求:
- 該控件可以左右滑動。
- 底部積分是一個等差數列,可以自己定義。積分初始爲半透明,小紅旗下方顯示設定的最大值。小火箭會飛到當前用戶對應的積分位置,用戶得到的積分在小火箭動畫之後會顯示爲白色,同時當前積分位置出現一條標識線。
- 動畫開始的時候小火箭會從0開始移動,直到當前積分位置,在移動過程中小火箭會有一個噴射火焰的效果。
- 背景會隨着火箭的移動而移動,當動畫結束的時候,保證小火箭在屏幕中心。
實現方式:
自己寫一個類繼承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回調中調用,保證在控件加載完成之後再設置參數。
到這裏這個控件就基本完成了。其實還有很多可以優化的地方,比如把一些屬性抽離出來,寫成自定義屬性,還有下標根據傳入數組動態生成等等,有興趣的朋友可以交流一下。源碼地址。