Android自定义View——自由定制优惠券背景

1、功能介绍

现在购物类的APP真的是数不甚数啊,经常可以在这些APP中看到优惠券的影子,今天我们就来实现一下优惠券的背景效果。实际开发中,如果我们想偷懒,直接用一张背景图作为优惠劵背景就OK了,今天我们手动来实现一下,其实实现起来还是比较简单的。效果图如下:

边缘的样式可以自由定制,有两种边缘类型:半圆形和三角形。上面图中第一张左右两边边缘为三角形、上下边缘为半圆形,第二张左右两本是半圆形、上下两边是三角形,第三张上下、左右两边都是半圆形。边缘的设置都是通过在xml布局中自定义属性指定,因此可根据需要自由定制,也可以设置为none,表示不绘制边缘,默认类型就是none。

2、实现思路

要实现这个自定义View,我们需要考虑的问题有三个:

(1)是继承View还是ViewGroup?

(2)边缘怎么来处理?

(3)绘制的时机?

对于第一个问题,我们简单想一下就可以得出答案,因为我们在这个自定义View中需要放入一些其他的控件,如上图中的TextView和ImageView,如果继承View那里面就不能再放入其他控件了,因此我们得选择继承ViewGroup,但是如果我们直接是继承ViewGroup的话,那里面的控件摆放逻辑即onLayout还得我们自己来实现,此时就会比较复杂了,为了简单起见,我们直接继承自LinearLayout即可,不用去考虑onMeasure和onLayout的实现,只需关注我们这一次的业务核心即绘制不同的边缘。

对于第二个问题,我们观察一下上图,只需要在控件的边缘做文章即可,拿半圆形边缘来说,我们只需要在一条边上循环进行半圆的绘制即可,这里重要的是要计算出这一条边上有多少个半圆型,因为每个人可能设置的控件长度不一样,我们可以指定一下圆形半径以及半圆形与半圆形直接间距,有了这两个参数就可以计算出这一条边上有多少个半圆型了,如下计算方法:

/** * 计算垂直方向需要画圆或三角形的数量和初始偏移量 * @param gapSize 每个圆形或三角形之间的间距 */private void calculateVerticalCount(float gapSize){    mVerticalCount = (int) ((mHeight - gapSize) / (2 * mRadius + gapSize));    mVerticalInitSize = (int) ((mHeight - (2 * mRadius * mVerticalCount + (mVerticalCount + 1) * gapSize)) / 2);}

/** * 计算水平方向上圆或三角形的数量和初始偏移量 * @param gapSize 每个圆形或三角形之间的间距 */private void calculateHorizontalCount(float gapSize) {    mHorizontalCount = (int) ((mWidth - gapSize) / (2 * mRadius + gapSize));    mHorizontalInitSize = (int) ((mWidth - (2 * mRadius * mHorizontalCount + (mHorizontalCount + 1) * gapSize)) / 2);}

上面这个计算方式画个图就出来了,假设有n个半圆,那就会有n+1个圆间距,因此控件宽度mWidth = n * 2 * radius + (n-1) * gapSize,由此可以求出半圆的个数,由于边的宽度或高度在这个半径radius和圆间距gapsize下不一定能够完整平分,为了让两边留出的距离相等,我们计算了一个初始偏移值。

在计算出了边上的半圆数量之后,就可以开始准备绘制操作了。那绘制的时机是在哪里?根据View的绘制过程:先绘制背景、在绘制自己(即onDraw)、下面绘制子布局、最后再绘制一些装饰之类的,其实我们在onDraw或dispatchDraw中绘制都可以,只不过onDraw一般是对于View来说的,对于ViewGroup我们一般用它来摆放布局,当我们在ViewGroup的onDraw中绘制时,最好要给这个ViewGroup设置background或setWillNotDraw(false);进行设置,才能保证每次都会进入onDraw方法,这里我选择在dispatchDraw中进行绘制,先让它的子View全部绘制完毕,然后再绘制边缘,以避免我们的边缘被里面的内容给覆盖掉。

在绘制的时候不断循环绘制,不断更改坐标即可。具体实现如下:

@Overrideprotected void dispatchDraw(Canvas canvas) {    super.dispatchDraw(canvas);    if(vertical_style == 1){//如果垂直方向是半圆形        drawVerticalCircle(canvas);    }else if(vertical_style == 2){//垂直方向是三角形        drawVerticalTriangle(canvas);    }    if(horizontal_style == 1){//如果水平方向是半圆形        drawHorizontalCircle(canvas);    }else if(horizontal_style == 2){//如果水平方向是三角形        drawHorizontalTriangle(canvas);    }}

/** * 在水平方向上绘制三角形 * @param canvas */private void drawHorizontalTriangle(Canvas canvas) {    //先计算出水平方向上的数量    calculateHorizontalCount(0);    Path path = new Path();    float x = 0;    //绘制上面部分    for(int i = 0; i < mHorizontalCount; i++){        path.reset();        x = mHorizontalInitSize + i * 2 * mRadius;        path.moveTo(x, 0);        x += mRadius;        path.lineTo(x, mRadius);        x += mRadius;        path.lineTo(x, 0);        path.close();        canvas.drawPath(path, mPaint);    }    //绘制下面部分    x = 0;    for(int i = 0; i < mHorizontalCount; i++){        path.reset();        x = mHorizontalInitSize + i * 2 * mRadius;        path.moveTo(x, mHeight);        x += mRadius;        path.lineTo(x, mHeight - mRadius);        x += mRadius;        path.lineTo(x, mHeight);        path.close();        canvas.drawPath(path, mPaint);    }}

/** * 在水平方向上绘制圆形 * @param canvas */private void drawHorizontalCircle(Canvas canvas) {    //先计算出水平方向上的数量    calculateHorizontalCount(mGapSize);    float x = mHorizontalInitSize + mGapSize + mRadius;    //先绘制上面部分    for(int i = 0; i < mHorizontalCount; i++){        canvas.drawCircle(x, 0, mRadius, mPaint);        x += 2 * mRadius + mGapSize;    }    //再绘制下面部分    x = mHorizontalInitSize + mGapSize + mRadius;    for(int i = 0; i < mHorizontalCount; i++){        canvas.drawCircle(x, mHeight, mRadius, mPaint);        x += 2 * mRadius + mGapSize;    }}

/** * 在垂直方向绘制三角形 * @param canvas */private void drawVerticalTriangle(Canvas canvas) {    //计算一下三角形的数量和初始距离    calculateVerticalCount(0);    Path path = new Path();    float y = 0;    //先画左边    for(int i = 0; i < mVerticalCount; i++){        path.reset();        y = mVerticalInitSize + i * 2 * mRadius;        path.moveTo(0, y);        y += mRadius;        path.lineTo(mRadius, y);        y += mRadius;        path.lineTo(0, y);        path.close();        canvas.drawPath(path, mPaint);    }    //再画右边    y = 0;    for(int i = 0; i < mVerticalCount; i++){        path.reset();        y = mVerticalInitSize + i * 2 * mRadius;        path.moveTo(mWidth, y);        y += mRadius;        path.lineTo(mWidth - mRadius, y);        y += mRadius;        path.lineTo(mWidth, y);        path.close();        canvas.drawPath(path, mPaint);    }}

/** * 在垂直方向绘制半圆形 * @param canvas */private void drawVerticalCircle(Canvas canvas) {    //计算一下圆形的数量和初始偏移距离    calculateVerticalCount(mGapSize);    //这次使用画弧来绘制出圆形    RectF rectF = new RectF();    //先画左边    for(int i = 0; i < mVerticalCount; i++){        rectF.left = -mRadius;        rectF.top = mVerticalInitSize + mGapSize * (i + 1) + i * 2 * mRadius;        rectF.right =  mRadius;        rectF.bottom = rectF.top + 2 * mRadius;        canvas.drawArc(rectF, -90, 180, false, mPaint);    }    //再画右边    for(int i = 0; i < mVerticalCount; i++){        rectF.left = mWidth - mRadius;        rectF.top = mVerticalInitSize + mGapSize * (i + 1) + i * 2 * mRadius;        rectF.right = rectF.left + 2 * mRadius;        rectF.bottom = rectF.top + 2 * mRadius;        canvas.drawArc(rectF, 90, 180, false, mPaint);    }}

3、使用方法

因为这个自定义View是继承自LinearLayout,在布局中直接把它当做LinearLayout来使用,通过我们自定义的属性来指定边缘类型。

<declare-styleable name="CouponStyle">    <attr name="vertical_style">        <enum name="none" value="0"/>        <enum name="circle" value="1" />        <enum name="triangle" value="2"/>    </attr>    <attr name="horizontal_style">        <enum name="none" value="0"/>        <enum name="circle" value="1" />        <enum name="triangle" value="2"/>    </attr></declare-styleable>

那在xml布局中就可以自己自由指定了,如下:

<com.scu.lly.couponbgdemo.view.CouponBgView    android:layout_width="match_parent"    android:layout_height="200dp"    android:layout_marginLeft="20dp"    android:layout_marginRight="20dp"    android:orientation="horizontal"    android:background="#47BDBD"    android:gravity="center_vertical"    coupon:horizontal_style="circle" <!-- 指定水平方向边缘的类型 -->    coupon:vertical_style="triangle">    <LinearLayout        android:layout_width="0dp"        android:layout_weight="1"        android:layout_height="match_parent"        android:padding="30dp"        android:orientation="vertical">        <TextView            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:textColor="@color/white"            android:textSize="18sp"            android:text="顺旺基优惠券"/>        <TextView            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:textColor="#FC4A36"            android:layout_marginTop="15dp"            android:textSize="16sp"            android:text="全场五折优惠"/>        <TextView            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:textColor="@color/white"            android:layout_marginTop="15dp"            android:textSize="15sp"            android:text="券编号:2016070920160720"/>        <TextView            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:textColor="@color/white"            android:layout_marginTop="15dp"            android:textSize="15sp"            android:text="有效期:2016-07-09至2016-07-20"/>    </LinearLayout>    <ImageView        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_marginRight="20dp"        android:src="@mipmap/iv_coupon"        android:layout_gravity="center"/></com.scu.lly.couponbgdemo.view.CouponBgView>

点击下载

使用起来是不是很简单呢?

在翻看购物类APP时,看到了很多APP在添加购物车时有很多添加动画,那么下一篇就实现一下购物车添加动画玩玩。

时间: 2024-10-07 19:13:54

Android自定义View——自由定制优惠券背景的相关文章

Android自定义View(CustomCalendar-定制日历控件)

转载请标明出处: http://blog.csdn.net/xmxkf/article/details/54020386 本文出自:[openXu的博客] 目录: 1分析 2自定义属性 3onMeasure 4onDraw 绘制月份 绘制星期 绘制日期及任务 5事件处理 源码下载 ??应项目需求,需要做一个日历控件,效果图如下: ???? ??接到需求后,没有立即查找是否有相关开源日历控件可用.系统日历控件是否能满足 ,第一反应就是这个控件该怎么画?谁叫咱自定义控件技术牛逼呢O(∩_∩)O哈哈~

Android 自定义View合集

自定义控件学习 https://github.com/GcsSloop/AndroidNote/tree/master/CustomView 小良自定义控件合集 https://github.com/Mr-XiaoLiang 自定义控件三部曲 http://blog.csdn.net/harvic880925?viewmode=contents Android 从0开始自定义控件之View基础知识与概念 http://blog.csdn.net/airsaid/article/details/5

android 自定义view 前的基础知识

本篇文章是自己自学自定义view前的准备,具体参考资料来自 Android LayoutInflater原理分析,带你一步步深入了解View(一) Android视图绘制流程完全解析,带你一步步深入了解View(二) Android视图状态及重绘流程分析,带你一步步深入了解View(三) Android自定义View的实现方法,带你一步步深入了解View(四) 这位大哥的系列博文,相当于自己看这些的一个思考吧. 一.首先学layoutInflater. 相信接触Android久一点的朋友对于La

Android自定义View(二、深入解析自定义属性)

转载请标明出处: http://blog.csdn.net/xmxkf/article/details/51468648 本文出自:[openXu的博客] 目录: 为什么要自定义属性 怎样自定义属性 属性值的类型format 类中获取属性值 Attributeset和TypedArray以及declare-styleable ??在上一篇博客<Android自定义View(一.初体验)>中我们体验了自定义控件的基本流程: 继承View,覆盖构造方法 自定义属性 重写onMeasure方法测量宽

Android自定义View,你必须知道的几点

为什么我们觉得自定义View是学习Android的一道坎? 为什么那么多Android大神却认为自定义View又是如此的简单? 为什么google随便定义一个View都是上千行的代码? 以上这些问题,相信学Android的同学或多或少都有过这样的疑问. 那么,看完此文,希望对你们的疑惑有所帮助. 回到主题,自定义View ,需要掌握的几个点是什么呢? 我们先把自定义View细分一下,分为两种 1) 自定义ViewGroup 2) 自定义View 其实ViewGroup最终还是继承之View,当然

android自定义View之NotePad出鞘记

现在我们的手机上基本都会有一个记事本,用起来倒也还算方便,记事本这种东东,如果我想要自己实现,该怎么做呢?今天我们就通过自定义View的方式来自定义一个记事本.OK,废话不多说,先来看看效果图. 整个页面还是很简单的. 1.自定义View的分类 OK,那么在正文开始之前,我想先来说说自定义View的分类,自定义View我们一共分为三类 1.自绘控件 自绘控件就是我们自定义View继承自已有控件,然后扩展其功能,之前两篇自定义View的博客(android自定义View之钟表诞生记,android

android自定义View之仿通讯录侧边栏滑动,实现A-Z字母检索

我们的手机通讯录一般都有这样的效果,如下图: OK,这种效果大家都见得多了,基本上所有的Android手机通讯录都有这样的效果.那我们今天就来看看这个效果该怎么实现. 一.概述 1.页面功能分析 整体上来说,左边是一个ListView,右边是一个自定义View,但是左边的ListView和我们平常使用的ListView还有一点点不同,就是在ListView中我对所有的联系人进行了分组,那么这种效果的实现最常见的就是两种思路: 1.使用ExpandableListView来实现这种分组效果 2.使

Android自定义View效果目录

1.绚丽的loading动效的实现 2.Android自定义View:进度条+冒泡文本 3.Android雷达图(蜘蛛网图) 4.Android文本闪烁 5.Android绘制圆形进度条 6.重写TextView,实现圆形背景,文本居中显示

Android 自定义View之随机生成图片验证码

本篇文章讲的是Android自定义View之随机生成图片验证码,开发中我们会经常需要随机生成图片验证码,但是这个是其次,主要还是想总结一些自定义View的开发过程以及一些需要注意的地方. 按照惯例先看看效果图: 一.先总结下自定义View的步骤: 1.自定义View的属性 2.在View的构造方法中获得我们自定义的属性 3.重写onMesure 4.重写onDraw 其中onMesure方法不一定要重写,但大部分情况下还是需要重写的 二.View 的几个构造函数 1.public CustomV