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

总结

以上所述是小编给大家介绍的Android 自定义View之边缘凹凸的优惠券效果的开发过程,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我们网站的支持!

时间: 2018-03-20

Android自定义View 仿QQ侧滑菜单的实现代码

先看看QQ的侧滑效果 分析一下 先上原理图(不知道能否表达的清楚 ==) -首先这里使用了 Android 的HorizontalScrollView 水平滑动布局作为容器,当然我们需要继承它自定义一个侧滑视图 - 这个容器里面有一个父布局(一般用LinerLayout,本demo用的是),这个父布局里面有且只有两个子控件(布局),初始状态菜单页的位置在Y轴上存在偏移这样可以就可以形成主页叠在菜单页的上方的视觉效果:然后在滑动的过程程中 逐渐修正偏移,最后菜单页和主页并排排列.原理搞清了实现起来

Android自定义View圆形和拖动圆、跟随手指拖动效果

单纯的自定义一个圆非常简单 只需要几步就完成 拖动圆添加实现触摸事件即可 我在第一次自定义View圆遇到的几个Bug: 1.拖动圆的话在xml里面设置的自定义圆的宽和高是它能活动的空间的大小 不是圆控件的大小 如果你定义了100dp 拖动它的时候超过100dp这个距离这个圆就会看不见 就像下面这样 如果想活动于整个屏幕直接给宽和高match_parent属性就好了 2.我在定义充满属性match_parent的时候运行会报错,什么方法都用了就是不行,耐心等待过一会就好了-有可能是studio没来

Android 自定义TextView去除paddingTop和paddingBottom

Android 自定义TextView去除paddingTop和paddingBottom 最近项目中需要用libgdx渲染一个Android的TextView, 但是绘制出来的TextView总是默认带有paddingTop和paddingBottom, 如下图所示: 网上有很多解决方案,例如在xml中设置如下属性: android:lineSpacingMultiplier="0.8" android:includeFontPadding="false" 或者设

Android自定义TextView仿微信朋友圈文字展开全文功能

Android自定义TextView仿微信朋友圈文字信息,展开全文功能 代码及注释如下: 首先写一个xml文件 showmore.xml: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical

Android自定义textview实现竖直滚动跑马灯效果

本文实例为大家分享了Android自定义textview实现跑马灯效果的具体代码,供大家参考,具体内容如下 xml布局 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.c

Android自定义TextView实现文字图片居中显示的方法

最近有个需求是这样的,人民币的符号"¥"因为安卓手机系统的不一致导致符号不是完全一样,所以用美工的给的图片代替,考虑到用的地方比较多,所以想着写一个继承于线性布局的组合控件,后来一想,安卓中不是有TextView吗,这个自带图片的控件,后来写了个demo,因为我是用的MatchParent,导致问题出现,人民币符号不是和文字一样的居中,因此才有了这篇博文,让我们来自定义TextView吧,这个场景用的比较多. 分析下TextView的源码 我们先来分析下TextView的源码,因为Te

Android 自定义TextView实现滑动解锁高亮文字

下面一段代码给大家分享Android 自定义TextView实现滑动解锁高亮文字效果,具体代码如下所示: public class HightLightTextView extends TextView { // 存储view的宽度 private int mTextViewWidth = 0; // 画笔 private Paint mPaint; // 线性渲染 private LinearGradient mLinearGradient; // 存储变换的matrix private Ma

Android 自定义Dialog去除title导航栏的解决方法

如下所示: Dialog dialog = new Dialog(context); dialog.requestWindowFeature(Window.FEATURE_NO_TITLE); dialog.setContentView(view); 以上这篇Android 自定义Dialog去除title导航栏的解决方法就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持我们.

Android自定义TextView实现drawableLeft内容居中

如何实现使用TextView的DrawableLeft使图片和文字居中显示呢??? 代码如下: 1.首先自定义一个类,继承TextView package com.test.signcalendar.weight; import android.content.Context; import android.graphics.Canvas; import android.graphics.drawable.Drawable; import android.util.AttributeSet; i

Android自定义TextView跑马灯效果

Android自带的跑马灯效果不太好控制,还必须要满足条件才能有效果,而且速度不受控制.前面我的博客中有一篇就是用Android自带的跑马灯效果的,但是基于不同的使用效果,这里在网上找到了一个更好的方法.沿用了作者的一些方法,但是添加了更好的扩展功能,和大家一起分享.这里面有控制往左往右两个方向的实现. 1.首先是简单的布局main.xml <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android&quo

Android 自定义TextView实现文本内容自动调整字体大小

最近做通讯录小屏机 联系人姓名显示--长度超过边界字体变小 /** * 自定义TextView,文本内容自动调整字体大小以适应TextView的大小 * @author yzp */ public class AutoFitTextView extends TextView { private Paint mTextPaint; private float mTextSize; public AutoFitTextView(Context context) { super(context); }

Android自定义TextView实现文字倾斜效果

前言 由于Android自带的TextView控件没有提供倾斜的(我暂时没有找到),我们可以自定义控件来实现,下面首先来看我们实现的效果图. TextView文字倾斜 其实实现很简单,下面我们来看实现步骤: 1.新建一个类 LeanTextView继承TextView public class LeanTextView extends TextView { public int getmDegrees() { return mDegrees; } public void setmDegrees(