Android自定义View之导航指示Indicator

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动作效果了。效果图



项目下载

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值