Android自定义View相信大家都很熟悉了,导航指示图标(很多应用开始界面小圆点之类)实现的方法也有很多(很多人用layout动态添加ImageView),那为什么还要用自定义View呢。
原因是一同事遇到那种个数不确定的页面数,需要从网络或者其他地方获取页面数,那么就必须要在Activity中添加很多动态添加ImageView的逻辑代码。其次是因为博主曾经面试过淘宝,面试官问到如何抽象封装代码时,说了一大堆,最后问面试官有什么好方法时,说通过自定义ViewGroup或View对功能逻辑代码封装。
自定义View的好处有哪些呢,个人觉得有:逻辑代码分离、可扩展性强、动态性灵活、创建较少java对象
首先添加一些自定义View需要的属性:
在功能目录values添加attrs.xml,如果已经存在之需要在原来文件进行添加
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="Indicator">
<!-- 当前索引图标 -->
<attr name="actived" format="reference" />
<!-- 非当前索引图标 -->
<attr name="unactived" format="reference" />
<!-- 指示标志间距 -->
<attr name="space" format="dimension" />
<attr name="total" format="integer"/>
</declare-styleable>
</resources>
然后定义一个IndicatorView继承View
package com.example.customcomponent;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
public class IndicatorView extends View{
private static final String TAG = "IndicatorView";
private Bitmap mActive;
private Bitmap mUnActive;
private int current = 0;
private int total = 0;
private int bitmapWidth = 0;
private int bitmapHeight = 0;
private int space = 0;
private Paint paint = new Paint();
public IndicatorView(Context context){
this(context,null);
}
// 重写这个方法就可以在布局文件中定义添加这个自定义View
public IndicatorView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs,R.styleable.Indicator);
// 从xml布局文件中读取属性
Drawable active = a.getDrawable(R.styleable.Indicator_actived);
Drawable unactive = a.getDrawable(R.styleable.Indicator_unactived);
if (active != null){
mActive = ((BitmapDrawable)active).getBitmap();
bitmapWidth = mActive.getWidth();
bitmapHeight = mActive.getHeight();
}
if (unactive != null){
mUnActive = ((BitmapDrawable)unactive).getBitmap();
}
space = a.getDimensionPixelSize(R.styleable.Indicator_space, 0);
total = a.getInteger(R.styleable.Indicator_total, 0);
a.recycle();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int w;
int h;
if (mActive == null || mUnActive == null) {
bitmapWidth = -1;
bitmapHeight = -1;
w = h = 0;
} else {
w = bitmapWidth*total + space*(total-1);
h = bitmapHeight;
if (w <= 0) w = 1;
if (h <= 0) h = 1;
}
int widthSize = resolveSizeAndState(w, widthMeasureSpec, 0);
int heightSize = resolveSizeAndState(h, heightMeasureSpec, 0);
Log.d(TAG, "widthSize="+widthSize+" heightSize="+heightSize);
setMeasuredDimension(widthSize, heightSize);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right,
int bottom) {
Log.d(TAG, "onlayout:left="+left+" top="+top+" right="+right+" bottom="+bottom);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mActive == null || mUnActive == null){
Log.d(TAG, "do you set image src");
return;
}
//此处通知android系统将图片按我们需要的逻辑画到屏幕上
for (int i = 0; i < total ; i++){
if(i == current){
canvas.drawBitmap(mActive, i*bitmapWidth+space*i, 0, paint);
} else {
canvas.drawBitmap(mUnActive, i*bitmapWidth+space*i, 0, paint);
}
}
}
/**
* 设置指示图标
* @param active
* @param unActive
*/
public void setImageSrc(Drawable active, Drawable unActive){
this.mActive = ((BitmapDrawable)active).getBitmap();
this.mUnActive = ((BitmapDrawable)unActive).getBitmap();
updateSrc();
}
/**
* 更新指示个数
* @param total
*/
public void updateTotal(int total){
this.total = total;
requestLayout();
invalidate();
}
/**
* 设置当前处于第几个
* @param curr
*/
public void setCurr(int curr){
this.current = curr;
}
/**
* 设置间隔
* @param space
*/
public void setSpace(int space){
this.space = space;
}
public void next(){
if (current == (total - 1)){
this.current = 0;
} else {
this.current += 1;
}
invalidate();
}
public void prev(){
if (current == 0) {
this.current = total-1;
} else {
this.current -= 1;
}
invalidate();
}
private void updateSrc(){
bitmapWidth = mActive.getWidth();
requestLayout();
invalidate();
}
}
下面就可以在布局文件中添加这个View了,当然通过代码添加也未尝不可
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:indicat="http://schemas.android.com/apk/res/com.example.customcomponent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin" >
<com.example.customcomponent.IndicatorView
android:id="@+id/indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
indicat:actived="@drawable/page_indicator_focused"
indicat:unactived="@drawable/page_indicator_unfocused"
indicat:space="6dip"
indicat:total="5"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="20dip" />
<LinearLayout android:layout_above="@id/indicator"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<EditText android:id="@+id/ed_num"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<Button android:id="@+id/btn_total"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="更新总数"/>
<Button android:id="@+id/btn_prev"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="前一个"/>
<Button android:id="@+id/btn_next"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="后一个"/>
</LinearLayout>
</RelativeLayout>
上面红色部分很重要,如果
xmlns:indicat="http://schemas.android.com/apk/res/com.example.customcomponent"
没有添加的话
indicat:actived="@drawable/page_indicator_focused"
indicat:unactived="@drawable/page_indicator_unfocused"indicat:space="6dip"
indicat:total="5"
这几个属性就会报错。
至此关键代码已经完成。
下面是Activity部分代码
package com.example.customcomponent;
import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
public class MainActivity extends Activity implements OnClickListener{
IndicatorView indicator;
EditText edTotal;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
indicator = (IndicatorView) findViewById(R.id.indicator);
edTotal = (EditText) findViewById(R.id.ed_num);
Button btnTotal = (Button) findViewById(R.id.btn_total);
btnTotal.setOnClickListener(this);
Button btnPrev = (Button) findViewById(R.id.btn_prev);
btnPrev.setOnClickListener(this);
Button btnNext = (Button) findViewById(R.id.btn_next);
btnNext.setOnClickListener(this);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_total:
indicator.updateTotal(Integer.parseInt(edTotal.getText().toString()));
break;
case R.id.btn_next:
indicator.next();
break;
case R.id.btn_prev:
indicator.prev();
break;
default:
break;
}
}
}
好了,感兴趣的同学把代码拷贝下来试一下吧,然后再改改代码很容易就能实现GIF动作效果了。效果图