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在添加购物车时有很多添加动画,那么下一篇就实现一下购物车添加动画玩玩。