Android自定义控件View(三)组合控件
不少人应该见过小米手机系统音量控制UI,一个圆形带动画效果的音量加减UI,效果很好看。它是怎么实现的呢?这篇博客来揭开它的神秘面纱。先上效果图
相信很多人都知道Android自定义控件的三种方式,Android自定义控件View(一)自绘控件,Android自定义控件View(二)继承控件,还有就是这一节即将学习到的组合控件。我们通过实现圆形音量UI来讲解组合控件的定义和使用。
组合控件
所谓组合控件就是有多个已有的控件组合而成一个复杂的控件。比如上图的音量控件就是一个完美的组合控件。我们来分析一下,音量组合控件是由哪些子控件组合而成的?中间有一个ImageView和一个TextView实现,背景是有一个半透明圆形和白色圆环叠加构成的(我们暂且叫音量控件VolumeView)。因此音量组合控件(VolumeViewLayout)就是有3个子控件组合而成:VolumeView,ImageView,TextView。代码实现如下:
package com.xjp.customvolumeview;
import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
/**
* Description:组合布局实现类似小米手机音量UI
* User: xjp
* Date: 2015/5/29
* Time: 18:06
*/
public class VolumeViewLayout extends FrameLayout {
private VolumeView volumeView;
private ImageView icon;
private TextView title;
public VolumeViewLayout(Context context) {
this(context, null);
}
public VolumeViewLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public VolumeViewLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
LayoutInflater inflater = LayoutInflater.from(context);
View view = inflater.inflate(R.layout.volume_view_layout, this);
volumeView = (VolumeView) view.findViewById(R.id.volume);
icon = (ImageView) view.findViewById(R.id.img_volume);
title = (TextView) view.findViewById(R.id.text);
}
/**
* 设置标题
*
* @param msg
*/
public void setTitle(String msg) {
title.setText(msg);
}
/**
* 设置图片
*
* @param resId
*/
public void setIcon(int resId) {
icon.setImageResource(resId);
}
/**
* 加音量
*/
public void volumeUp() {
volumeView.volumeUp();
}
/**
* 减音量
*/
public void volumeDown() {
volumeView.volumeDown();
}
}
VolumeViewLayout类中的构造方法通过LayoutInflater加载XML布局来构成一个组合控件,因此可以看出,如果你需要修改组合控件显示效果的话,你可以修改LayoutInflater加载XML布局就ok了。VolumeViewLayout是继承FrameLayout,你可以继承任何ViweGroup的父容器View。
VolumeViewLayout暴露出4个方法,分别是设置中间的Image图片,设置中间的文字,和音量加减操作方法。布局代码中这么使用:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/back"
tools:context=".MainActivity">
<Button
android:id="@+id/buttonAdd"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="80dp"
android:layout_marginTop="55dp"
android:text="音量+" />
<Button
android:id="@+id/buttonDelete"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginTop="55dp"
android:layout_toRightOf="@+id/buttonAdd"
android:text="音量-" />
<com.xjp.customvolumeview.VolumeViewLayout
android:id="@+id/volumeView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"></com.xjp.customvolumeview.VolumeViewLayout>
</RelativeLayout>
代码调用中这么使用:
package com.xjp.customvolumeview;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.view.View;
import android.widget.Button;
public class MainActivity extends ActionBarActivity implements View.OnClickListener {
private Button buttonAdd;
private Button buttonDelete;
private VolumeViewLayout volumeView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
buttonAdd = (Button) findViewById(R.id.buttonAdd);
buttonAdd.setOnClickListener(this);
buttonDelete = (Button) findViewById(R.id.buttonDelete);
buttonDelete.setOnClickListener(this);
volumeView = (VolumeViewLayout) findViewById(R.id.volumeView);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.buttonAdd:
volumeView.volumeUp();
break;
case R.id.buttonDelete:
volumeView.volumeDown();
break;
}
}
}
如需要改变音量UI中的图片和文字,可以分别调用如下方法即可
volumeView.setIcon(R.drawable.icon);
volumeView.setTitle("音乐音量");
以上就是真个组合控件实现的过程。我们来梳理一下流程:
- 在XML布局文件中定义好一个组合布局。
- 继承ViewGroup类自定义组合控件。
- 在自定义组合控件的构造方法中通过LayoutInflater加载组合布局。
- 在xml布局中使用组合控件。
自绘圆形带动画效果音量控件 VolumeView
整体上实现了组合控件。我们来看看音量控件VolumeView怎么实现的?其实VolumeView根据 Android自定义控件View(一)自绘控件来实现的。我们来回顾一下自绘控件的流程
- 自定义控件View的属性。
- 在View的构造方法中获得属性值。
- 重写onMeasure方法
- 重写onDraw方法
- 布局中使用自定义控件
自定义控件View的属性
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="radius" format="dimension"></attr>
<attr name="backgroundColor" format="color"></attr>
<attr name="primaryVolumeColor" format="color"></attr>
<attr name="volumeColor" format="color"></attr>
<attr name="borderWidth" format="dimension"></attr>
<attr name="maxVolume" format="integer"></attr>
<declare-styleable name="VolumeView">
<attr name="radius"></attr>
<attr name="backgroundColor"></attr>
<attr name="primaryVolumeColor"></attr>
<attr name="volumeColor"></attr>
<attr name="borderWidth"></attr>
<attr name="maxVolume"></attr>
</declare-styleable>
</resources>
在View的构造方法中获得属性值
/**
* 获取自定义View的属性值
*
* @param context
* @param attrs
*/
private void setAttrs(Context context, AttributeSet attrs) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.VolumeView);
if (null != a) {
radius = a.getDimensionPixelSize(R.styleable.VolumeView_radius, defaultRadius);
backgroundColor = a.getColor(R.styleable.VolumeView_backgroundColor, defaultBackgroundColor);
volumeColor = a.getColor(R.styleable.VolumeView_volumeColor, defaultVolumeColor);
primaryVolumeColor = a.getColor(R.styleable.VolumeView_primaryVolumeColor, defaultPrimaryVolumeColor);
borderWidth = a.getDimensionPixelSize(R.styleable.VolumeView_borderWidth, defaultBorderWidth);
maxVolume = a.getInt(R.styleable.VolumeView_maxVolume, 15);
a.recycle();
}
}
重写onMeasure方法
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
/**固定自定义圆形UI的大小,不管属性设置大小多少都不影响圆形UI大小,
唯一影响圆形UI的大小只有圆的半径,言外之意:
只能通过半径来控制圆形UI大小,所以属性里半径为必设值。*/
setMeasuredDimension(radius * 2, radius * 2);
}
重写onDraw方法
@Override
protected void onDraw(Canvas canvas) {
//绘制背景
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.FILL);
paint.setColor(backgroundColor);
radius = getWidth() / 2;
canvas.drawCircle(radius, radius, radius, paint);
//绘制音量线圈背景
paint.setAntiAlias(true);
paint.setColor(primaryVolumeColor);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(borderWidth);
canvas.drawCircle(radius, radius, radius - borderWidth, paint);
//绘制音量线圈
paint.setAntiAlias(true);
paint.setColor(volumeColor);
rectF = new RectF(borderWidth, borderWidth, getWidth() - borderWidth, getHeight() - borderWidth);
if (isVolumeUp) {//音量增加时
canvas.drawArc(rectF, -90, angle * (volumeNum > 0 ? volumeNum - 1 : 0) + unitAngle * fraction, false, paint);
} else {//音量减小时
canvas.drawArc(rectF, -90, angle * (volumeNum + 1) - unitAngle * fraction, false, paint);
}
}
XML布局中使用控件
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:orientation="vertical">
<com.xjp.customvolumeview.VolumeView
android:id="@+id/volume"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
custom:borderWidth="5dp"
custom:maxVolume="10"
custom:radius="65dp" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:orientation="vertical">
<ImageView
android:id="@+id/img_volume"
android:layout_width="58dp"
android:layout_height="48dp"
android:layout_gravity="center"
android:scaleType="fitXY"
android:src="@drawable/icon" />
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/img_volume"
android:layout_gravity="center"
android:layout_marginTop="8dp"
android:text="铃声音量"
android:textColor="@android:color/white"
android:textSize="13sp" />
</LinearLayout>
</RelativeLayout>
完整代码
package com.xjp.customvolumeview;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
/**
* Description:圆形音量控件
* User: xjp
* Date: 2015/5/29
* Time: 14:08
*/
public class VolumeView extends View {
private static final String TAG = "VolumeView";
private static final boolean DEBUG = false;
//圆形半径
private int radius = 0;
//音量边框底色
private int primaryVolumeColor = 0;
//音量边框颜色
private int volumeColor = 0;
//圆形音量背景颜色
private int backgroundColor = 0;
//音量边框宽度
private int borderWidth = 0;
//动画百分比
private int fraction = 0;
//以下都是默认值
private int defaultRadius = 60;
private int defaultBorderWidth = 8;
private int defaultBackgroundColor = 0x60000000;
private int defaultVolumeColor = Color.WHITE;
private int defaultPrimaryVolumeColor = 0x80000000;
private RectF rectF = null;
private Paint paint = null;
//最大音量次数
private int maxVolume = 15;
//音量每增加一次,对于的角度
private float angle = 0;
//动画的最大值
private int maxAnimationValue = 10;
//音量每增加一次的单位角度
private float unitAngle = 0;
//当前音量的次数
private int volumeNum = 0;
//是否是加音量
private boolean isVolumeUp = true;
public VolumeView(Context context) {
this(context, null);
}
public VolumeView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public VolumeView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setAttrs(context, attrs);
initPaint();
}
/**
* 初始化画笔
*/
private void initPaint() {
angle = 360f / maxVolume;
unitAngle = angle / maxAnimationValue;
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setAntiAlias(true);
paint.setDither(true);
}
/**
* 获取自定义View的属性值
*
* @param context
* @param attrs
*/
private void setAttrs(Context context, AttributeSet attrs) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.VolumeView);
if (null != a) {
radius = a.getDimensionPixelSize(R.styleable.VolumeView_radius, defaultRadius);
backgroundColor = a.getColor(R.styleable.VolumeView_backgroundColor, defaultBackgroundColor);
volumeColor = a.getColor(R.styleable.VolumeView_volumeColor, defaultVolumeColor);
primaryVolumeColor = a.getColor(R.styleable.VolumeView_primaryVolumeColor, defaultPrimaryVolumeColor);
borderWidth = a.getDimensionPixelSize(R.styleable.VolumeView_borderWidth, defaultBorderWidth);
maxVolume = a.getInt(R.styleable.VolumeView_maxVolume, 15);
a.recycle();
}
}
/**
* 设置圆形半径
*
* @param radius
*/
public void setRadius(int radius) {
this.radius = radius;
}
/**
* 设置音量边框的宽度
*
* @param borderWidth
*/
public void setBorderWidth(int borderWidth) {
this.borderWidth = borderWidth;
}
/**
* 设置最大音量值
*
* @param maxVolume
*/
public void setMaxVolume(int maxVolume) {
this.maxVolume = maxVolume;
}
/**
* 设置音量边框底色
*
* @param color
*/
public void setPrimaryVolumeColor(int color) {
primaryVolumeColor = color;
}
/**
* 设置音量边框颜色
*
* @param color
*/
public void setVolumeColor(int color) {
volumeColor = color;
}
/**
* 设置圆形音量的背景颜色
*
* @param color
*/
public void setBackgroundColor(int color) {
backgroundColor = color;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
/**固定自定义圆形UI的大小,不管属性设置大小多少都不影响圆形UI大小,
唯一影响圆形UI的大小只有圆的半径,言外之意:
只能通过半径来控制圆形UI大小,所以属性里半径为必设值。*/
setMeasuredDimension(radius * 2, radius * 2);
}
@Override
protected void onDraw(Canvas canvas) {
//绘制背景
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.FILL);
paint.setColor(backgroundColor);
radius = getWidth() / 2;
canvas.drawCircle(radius, radius, radius, paint);
//绘制音量线圈背景
paint.setAntiAlias(true);
paint.setColor(primaryVolumeColor);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(borderWidth);
canvas.drawCircle(radius, radius, radius - borderWidth, paint);
//绘制音量线圈
paint.setAntiAlias(true);
paint.setColor(volumeColor);
rectF = new RectF(borderWidth, borderWidth, getWidth() - borderWidth, getHeight() - borderWidth);
if (isVolumeUp) {//音量增加时
canvas.drawArc(rectF, -90, angle * (volumeNum > 0 ? volumeNum - 1 : 0) + unitAngle * fraction, false, paint);
} else {//音量减小时
canvas.drawArc(rectF, -90, angle * (volumeNum + 1) - unitAngle * fraction, false, paint);
}
}
/**
* 控制音量增加减少时的动画效果
*/
private void startAnim() {
ValueAnimator valueAnimator = ValueAnimator.ofInt(0, maxAnimationValue);
valueAnimator.setDuration(300);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
fraction = (int) animation.getAnimatedValue();
if (DEBUG) {
Log.e(TAG, "the fraction is " + fraction);
}
invalidate();
}
});
valueAnimator.start();
}
/**
* 加音量
*/
public void volumeUp() {
isVolumeUp = true;
if (volumeNum < maxVolume) {
volumeNum++;
startAnim();
}
}
/**
* 减音量
*/
public void volumeDown() {
isVolumeUp = false;
if (volumeNum > 0) {
volumeNum--;
startAnim();
}
}
}
VolumeView类暴露了很多方法,便于用户自定义圆形音量的UI风格。以上代码中实现了音量加减的动画效果,也就是如下代码:
/**
* 控制音量增加减少时的动画效果
*/
private void startAnim() {
ValueAnimator valueAnimator = ValueAnimator.ofInt(0, maxAnimationValue);
valueAnimator.setDuration(300);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
fraction = (int) animation.getAnimatedValue();
if (DEBUG) {
Log.e(TAG, "the fraction is " + fraction);
}
invalidate();
}
});
valueAnimator.start();
}
代码中通过属性动画监听动画更新接口获取每个时刻的动画值,根据这个值每次去重新绘制UI,也就是调用invalidate();之后系统会重新调用onDraw()方法绘制UI。
不了解属性动画这一块的童鞋可以参考前面关于属性动画的博客 Android属性动画Property Animation系列一之ValueAnimator。
以上就是全部的实现思路,代码就不一一解释了,毕竟有注释,效果还是很Nice~的。喜欢的童鞋,点赞吧!
~。
Android自定义控件View(三)组合控件的更多相关文章
- Android自定义控件之自定义组合控件
前言: 前两篇介绍了自定义控件的基础原理Android自定义控件之基本原理(一).自定义属性Android自定义控件之自定义属性(二).今天重点介绍一下如何通过自定义组合控件来提高布局的复用,降低开发 ...
- Android自定义控件之自定义组合控件(三)
前言: 前两篇介绍了自定义控件的基础原理Android自定义控件之基本原理(一).自定义属性Android自定义控件之自定义属性(二).今天重点介绍一下如何通过自定义组合控件来提高布局的复用,降低开发 ...
- android自定义控件(五) 自定义组合控件
转自http://www.cnblogs.com/hdjjun/archive/2011/10/12/2209467.html 代码为自己编写 目标:实现textview和ImageButton组合, ...
- Android 手机卫士--自定义组合控件构件布局结构
由于设置中心条目中的布局都很类似,所以可以考虑使用自定义组合控件来简化实现 本文地址:http://www.cnblogs.com/wuyudong/p/5909043.html,转载请注明源地址. ...
- Android自己定义View之组合控件 ---- LED数字时钟
先上图 LEDView效果如图所看到的. 之前看到一篇博客使用两个TextView实现了该效果.于是我想用自己定义控件的方式实现一个LEDView.使用时就可以直接使用该控件. 採用组合控件的方式,将 ...
- [android] 手机卫士自定义组合控件
设置中心 新建SettingActivity 设置GridView条目的点击事件 调用GridView对象的setOnItemClickListenner()方法,参数:OnItemClickList ...
- Android开发之自定义组合控件
自定义组合控件的步骤1.自定义一个View,继承ViewGroup,比如RelativeLayout2.编写组合控件的布局文件,在自定义的view中加载(使用View.inflate())3.自定义属 ...
- android:自己定义组合控件Weight(高仿猫眼底部菜单条)
在我们实际开发其中.会碰见一些布局结构类似或者同样的界面.比如应用的设置界面.tabbutton界面等. 这时候.对于刚開始学习的人来说,xml里面一个个绘制出来也许是最初的想法.可能随着经验的积累, ...
- Android 自定义控件之 日期选择控件
效果如下: 调用的代码: @OnClick(R.id.btn0) public void btn0() { final AlertDialog dialog = new AlertDialog.Bui ...
- Android 组合控件
前言 自定义组合控件就是多个控件组合起来成为一个新的控件,主要用来解决多次重复的使用同一类型的布局.比如我们应用的顶部的标题栏,还有弹出的固定样式的dialog,这些都是常用的,所以把他们所需要的控件 ...
随机推荐
- System and method for critical address space protection in a hypervisor environment
A system and method in one embodiment includes modules for detecting an access attempt to a critical ...
- uva 1456(dp)
题意:有n个数字u1,u2,u3-un,每一个数字出现的概率pi = ui/(u1 + u2 + - + un),分成w组.计算期望值. 第一组例子的五个数字例如以下 30 5 10 30 25 分成 ...
- Stack overflow 编译能通过,运行时出现Stack overflow
Stack overflow 编译能通过,运行时出现Stack overflow 大家都知道,Windows程序的内存机制大概是这样的,全局变量(局部的静态变量本质也属于此范围)存储于堆内存,该段内存 ...
- 【河南省多校脸萌第六场 A】巴什博弈?
[链接]http://acm.nyist.me/JudgeOnline/problem.php?cid=1013&pid=5 [题意] 在这里写题意 [题解] 0..a-1 YES a..a+ ...
- 【河南省多校脸萌第六场 B】点兵点将
[链接]点击打开链接 [题意] 在这里写题意 [题解] 先每个单位都不建造bi; 打死一个ai之后,把bi加入到大根堆里面. 然后等到不够打死某个单位的时候; 从大根堆里面取出最大的那个bi;不断取, ...
- Android多线程研究(6)——多线程之间数据隔离
在上一篇<Android多线程研究(5)--线程之间共享数据>中对线程之间的数据共享进行了学习和研究,这一篇我们来看看怎样解决多个线程之间的数据隔离问题,什么是数据隔离呢?比方说我们如今开 ...
- iOS_06_Mac os X
Mac os X 系统简介 * 苹果公司专门为苹果电脑设计的操作系统. * 以坚如磐石的UNIX为基础,既简单易用且功能强大. * x 是一个罗马数字正式的发音位“十”(ten),连续了先前的Mac ...
- multi_input_paths
- amazeui学习笔记--js插件(UI增强3)--折叠面板Collapse
amazeui学习笔记--js插件(UI增强3)--折叠面板Collapse 一.总结 注意点: 1.data-am-collapse:这个东西就是展开折叠事件 2.am-collapse(包括其下属 ...
- 3、Unicode\UTF-8\GBK 区别和联系
字符编码:Unicode和UTF-8之间的关系 可以参考下面blog:https://blog.csdn.net/xiaolei1021/article/details/52093706/ 这篇文章写 ...