Android 自定义View之边缘凹凸的优惠券效果

本篇文章讲的是自定义View之边缘凹凸的优惠券效果,之前有见过很多优惠券的效果都是使用了边缘凹凸的样式。和往常一样,主要总结一下在自定义View的开发过程中需要注意的一些地方。

按照惯例,我们先来看看效果图


这里写图片描述

一、写代码之前,我们先弄清楚view的启动过程:
之所以想要弄清楚这个问题是因为代码里面用到了onSizeChanged()方法,一开始我有点犹豫onSizeChanged是在什么时候启动的呢,所以看看View的启动流程吧

package per.lijuan.coupondisplayviewdome;

import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.LinearLayout;

/**
 * 自定义边缘凹凸的优惠券效果view
 * Created by lijuan on 2016/9/26.
 */
public class CouponDisplayView extends LinearLayout {

    public CouponDisplayView(Context context) {
        this(context, null);
        Log.d("mDebug", "CouponDisplayView:context");
    }

    public CouponDisplayView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
        Log.d("mDebug", "CouponDisplayView:context,attrs");
    }

    public CouponDisplayView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        Log.d("mDebug", "CouponDisplayView:context,attrs,defStyleAttr");
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        Log.d("mDebug", "onSizeChanged:w=" + w + ",h=" + h + ",oldw=" + oldw + ",oldh=" + oldh);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Log.d("mDebug", "onDraw");
    }

}

输出如下:

09-27 15:29:31.957 8210-8210/per.lijuan.coupondisplayviewdome D/mDebug: CouponDisplayView:context,attrs,defStyleAttr
09-27 15:29:31.957 8210-8210/per.lijuan.coupondisplayviewdome D/mDebug: CouponDisplayView:context,attrs
09-27 15:29:32.050 8210-8210/per.lijuan.coupondisplayviewdome D/mDebug: onSizeChanged:w=984,h=361,oldw=0,oldh=0
09-27 15:29:32.083 8210-8210/per.lijuan.coupondisplayviewdome D/mDebug: onDraw

在这里可以看到,onSizeChanged()方法的启动是在onDraw之前

二、view的几个常用触发方法

  1. onFinishInflate():当View中所有的子控件均被映射成xml后触发
  2. onMeasure(int widthMeasureSpec, int heightMeasureSpec):确定所有子元素的大小
  3. onLayout(boolean changed, int l, int t, int r, int b):当View分配所有的子元素的大小和位置时触发
  4. onSizeChanged(int w, int h, int oldw, int oldh):当view的大小发生变化时触发
  5. onDraw(Canvas canvas):负责将View绘制在屏幕上

三、View 的几个构造函数
1、public CouponDisplayView(Context context)
—>Java代码直接new一个CouponDisplayView实例的时候,会调用这个只有一个参数的构造函数;

2、public CouponDisplayView(Context context, AttributeSet attrs)
—>在默认的XML布局文件中创建的时候调用这个有两个参数的构造函数。AttributeSet类型的参数负责把XML布局文件中所自定义的属性通过AttributeSet带入到View内;

3、public CouponDisplayView(Context context, AttributeSet attrs, int defStyleAttr)
—>构造函数中第三个参数是默认的Style,这里的默认的Style是指它在当前Application或者Activity所用的Theme中的默认Style,且只有在明确调用的时候才会调用

4、public CouponDisplayView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)
—>该构造函数是在API21的时候才添加上的

自定义View中,我们需要重写了3个构造方法,在上面的构造方法中说过默认的布局文件调用的是两个参数的构造方法,所以记得让所有的构造方法调用三个参数的构造方法,然后在三个参数的构造方法中获得自定义属性。
一开始一个参数的构造方法和两个参数的构造方法是这样的:

    public CouponDisplayView(Context context) {
        super(context);
    }

    public CouponDisplayView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

我们需要注意的是super应该改成this,然后让一个参数的构造方法引用两个参数的构造方法,两个参数的构造方法引用三个参数的构造方法,代码如下:

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

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

四、分析具体的实现思路:
从上面的效果图来看,这个自定义View和普通的Linearlayout,RelativeLayout一样,只是上下两边多了类似于半圆锯齿的形状,我们需要在上下两条线上画一个个白色的小圆来实现这种效果。

假如我们上下线的半圆以及半圆与半圆之间的间距是固定的,那么不同尺寸的屏幕肯定会画出不同数量的半圆,那么我们只需要根据控件的宽度来获取能画的半圆数。

我们观察效果图会发现,圆的数量总是圆间距数量-1,也就是说,假设圆的数量是circleNum,那么圆间距就是circleNum+1,所以我们可以根据这个计算出circleNum:

circleNum = (int) ((w-gap)/(2*radius+gap)); 

这里gap就是圆间距,radius是圆半径,w是view的宽。

五、下面我们就开始来看看代码啦
1、自定义View的属性,首先在res/values/ 下建立一个attr.xml , 在里面定义我们的需要用到的属性以及声明相对应属性的取值类型

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!--圆间距-->
    <attr name="radius" format="dimension" />
    <!--半径-->
    <attr name="gap" format="dimension" />

    <declare-styleable name="CouponDisplayView">
        <attr name="radius" />
        <attr name="gap" />
    </declare-styleable>

</resources>

我们定义了圆间距和半径2个属性,format是值该属性的取值类型,format取值类型总共有10种,包括:string,color,demension,integer,enum,reference,float,boolean,fraction和flag。

2、然后在XML布局中声明我们的自定义View

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:custom="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_margin="16dp">

    <per.lijuan.coupondisplayviewdome.CouponDisplayView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#FBB039"
        android:orientation="horizontal"
        android:padding="16dp"
        custom:gap="8dp"
        custom:radius="5dp">

        <ImageView
            android:layout_width="90dp"
            android:layout_height="match_parent"
            android:scaleType="centerCrop"
            android:src="@mipmap/ic_launcher" />

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginLeft="5dp"
            android:orientation="vertical">

            <TextView
                android:id="@+id/name"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="电影新客代金劵"
                android:textSize="18dp" />

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:paddingTop="5dp"
                android:text="编号:525451122312431"
                android:textSize="12dp" />

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:paddingTop="5dp"
                android:text="满200元可用、限最新版本客户端使用"
                android:textSize="12dp" />

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:paddingTop="5dp"
                android:text="截止日期:2016-11-07"
                android:textSize="12dp" />
        </LinearLayout>
    </per.lijuan.coupondisplayviewdome.CouponDisplayView>
</LinearLayout>

一定要引入xmlns:custom=”http://schemas.android.com/apk/res-auto”,Android Studio中我们可以使用res-atuo命名空间,就不用添加自定义View全类名。

3、在View的构造方法中,获得我们的xml布局文件中定义的圆的半径和圆间距

private Paint mPaint;
    /**
     * 半径
     */
    private float radius=10;
    /**
     * 圆间距
     */
    private float gap=8;

    /**
     * 圆数量
     */
    private int circleNum;
    private float remain;

    public CouponDisplayView(Context context) {
        this(context, null);
        Log.d("mDebug", "CouponDisplayView context");
    }

    public CouponDisplayView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
        Log.d("mDebug", "CouponDisplayView context, attrs");
    }

    public CouponDisplayView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        Log.d("mDebug", "CouponDisplayView context,attrs,defStyleAttr");
        /**
         * 获得我们所定义的自定义样式属性
         */
        TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CouponDisplayView, defStyleAttr, 0);
        for (int i = 0; i < a.getIndexCount(); i++) {
            int attr = a.getIndex(i);
            switch (attr) {
                case R.styleable.CouponDisplayView_radius:
                    radius = a.getDimensionPixelSize(R.styleable.CouponDisplayView_radius, 10);
                    break;
                case R.styleable.CouponDisplayView_gap:
                    gap = a.getDimensionPixelSize(R.styleable.CouponDisplayView_radius, 8);
                    break;
            }
        }
        a.recycle();

        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setDither(true);
        mPaint.setColor(Color.WHITE);
        mPaint.setStyle(Paint.Style.FILL);
    }

4、重写onSizeChanged()方法,根据上面的圆的半径和圆间距来计算需要画的圆数量circleNum

@Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        Log.d("mDebug", "onSizeChanged,w=" + w + ",h=" + h + ",oldw=" + oldw + ",oldh=" + oldh);
        if (remain == 0) {
            //计算不整除的剩余部分
            remain = (int) (w - gap) % (2 * radius + gap);
        }
        circleNum = (int) ((w - gap) / (2 * radius + gap));
    }

5、接下来只需要重写onDraw()方法,简单的根据circleNum的数量将一个一个的圆绘制在屏幕上就可以了

 @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Log.d("mDebug", "onDraw");
        for (int i = 0; i < circleNum; i++) {
            float x = gap + radius + remain / 2 + ((gap + radius * 2) * i);
            canvas.drawCircle(x, 0, radius, mPaint);
            canvas.drawCircle(x, getHeight(), radius, mPaint);
        }
    }

这里remain/2是因为避免有一些情况:当计算出来的圆的数量不是整除时,这样就会出现右边最后一个间距会比其它的间距都要宽,所以我们在绘制第一个的时候加上了余下的间距的一半,即使是不整除的情况,至少也能保证第一个和最后一个间距宽度一致。

好了,本篇文章已经全部写完了,存在总结不到位的地方还望指导,感谢_

源码下载

参考资料:
http://blog.csdn.net/yissan/article/details/51429281

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容