wordpress 企业站,广州网页设计薪酬,学网站建设有什么用,网站维护年费目录 Android自定义控件一、对现有控件进行扩展二、创建复合控件1 定义属性2 组合控件3 引用UI模板 三、重写View来实现全新控件1 弧线展示图1.1 具体步骤#xff1a; 2 音频条形图2.1 具体步骤 四、补充#xff1a;自定义ViewGroup Android自定义控件 ref: Android自定义控件… 目录 Android自定义控件一、对现有控件进行扩展二、创建复合控件1 定义属性2 组合控件3 引用UI模板 三、重写View来实现全新控件1 弧线展示图1.1 具体步骤 2 音频条形图2.1 具体步骤 四、补充自定义ViewGroup Android自定义控件 ref: Android自定义控件 - 掘金 (juejin.cn) 自定义视图组件 | Android 开发者 | Android Developers (google.cn) 当系统控件不能满足我们的需求的时候这时候我们就需要自定义控件根据我们的需求来定制一个能满足我们需求的控件。 如果预构建的微件或布局都不能满足您的需求您可以创建自己的 View 子类。如果您只需要对现有微件或布局进行细微调整则只需将相应微件或布局子类化并替换其方法即可。 在自定义View时通常会重写onDraw()方法来绘制View的显示内容。
如果该View还需要使用wrap_content属性那么还必须重写onMeasure()方法。
通过自定义attrs属性还可以设置新的属性配置值。
自定义View的时候并不需要重写所有的方法只需要重写特定条件的回调方法即可。
在View中通常有以下一些比较重要的回调方法
onFinishInflate()从XML加载组件后回调。onSizeChanged()组件大小改变时回调。onMeasure()回调该方法来进行测量。onLayout()回调该方法来确定显示的位置。onTouchEvent()监听到触摸事件时回调。
在通常情况下有三种方法来实现自定义的控件
对现有控件进行拓展创建复合控件重写View来实现全新控件
一、对现有控件进行扩展 在原生控件的基础上进行拓展增加新的功能、修改显示的UI等 二、创建复合控件 继承一个合适的ViewGroup再给它添加指定功能的控件从而组合成新的复合控件创建出具体重用功能的控件集合 以一个通用的TopBar为示例
public class TopBar extends RelativeLayout {public TopBar(Context context) {this.TopBar(context, null);}public TopBar(Context context, AttributeSet attrs) {super(context, attrs);// 初始化的方法// 初始化属性initAttr(context, attrs)// 初始化布局initView(context);// 初如化事件initEvent();}}
1 定义属性
为一个View提供可自定义的属性非常简单只需要在res资源目录的values目录下创建一个attrs.xml的属性定义文件并在该文件中通过如下代码定义相应的属性即可。
?xml version1.0 encodingutf-8?resourcesdeclare-styleable nameTopBar !-- 确定引用的名称 --!-- 定义title文字大小颜色 --attr nametitle formatstring / !-- attr 标签来声明具体的自定义属性name声明属性名format来指定属性的类型 --attr nametitleTextSize formatdimension /attr nametitleTextColor formatcolor /!-- 定义left 文字大小颜色背景 --attr nameleftTextColor formatcolor /attr nameleftTextSize formatdimension /!-- 表示背景可以是颜色也可以是引用 --attr nameleftBackground formatreference|color /attr nameleftText formatstring /!-- 定义right 文字大小颜色背景 --attr namerightTextColor formatcolor /attr namerightTextSize formatdimension/attr namerightBackground formatreference|color / !-- 有些属性可以是颜色属性也可以是引用属性。比如按键的背景所以使用“|”来分隔不同的属性 --attr namerightText formatstring //declare-styleable/resources// 在TopBar的构造方法中通过如下所示代码来获取在XML布局文件中自定义的那些属性即与我们使用系统提供的那些属性一样。
// TypedArray ta context.obtainStyledAttributes(attrs, R.styleable.TopBar);
系统提供TypedArray来获取自定义属性集后面引用的styleable的TopBar就是在XML中通过所指定的name名。通过TypeArray对象的getString()、getColor()等方法就可以获取这些定义的属性值。
需要注意的是当获取完所有的属性值后需要调用TypedArray的recyle()方法来完成资源的回收。
public class TopBar extends RelativeLayout {private int mLeftTextColor;private Drawable mLeftBackground;private String mLeftText;private float mLeftTextSize;private int mRightTextColor;private Drawable mRightBackground;private String mRightTextSize;private float mRightTextSize;private String mTitleText;private float mTitleTextSize;private int mTitleTextColor;private void initAttr(Context context, AttributeSet attrs) {// 通过这个方法将你在attrs.xml中定义的declare-styleable的所有属性的值存储到TypedArray.TypedArray ta context.obtainStyledAttributes(attrs, R.styleable.TopBar);// 从TypedArray中取出对应的值来为要设置的属性赋值mLeftTextColor ta.getColor(R.styleable.TopBar_leftTextColor, 0);mLeftBackground ta.getDrawable(R.styleable.TopBar_leftBackground);mLeftText ta.getString(R.styleable.TopBar_leftText);mLeftTextSize typed.getDimension(R.styleable.TitleBar_leftTextSize, 20);mRightTextColor ta.getColor(R.styleable.TopBar_rightTextColor, 0);mRightBackground ta.getDrawable(R.styleable.TopBar_rightBackground);mRightText ta.getString(R.styleable.TopBar_rightText);mRightTextSize typed.getDimension(R.styleable.TitleBar_rightTextSize, 20);mTitleText ta.getString(R.styleable.TopBar_titleText);mTitleTextSize ta.getDimension(R.styleable.TopBar_titleTextSize, 10);mTitleTextColor ta.getColor(R.styleable.TopBar_titleTextColor, 0);// 获取完TypedArray的值后一般要调用recyle()方法来避免重新创建的时候的错误ta.recycle();}public TopBar(Context context) {this.TopBar(context, null);}public TopBar(Context context, AttributeSet attrs) {super(context, attrs);// 初始化的方法// 初始化属性initAttr(context, attrs)// 初始化布局initView(context);// 初如化事件initEvent();}}
2 组合控件
TopBar由三个控件组成左边按钮mLeftButton、右边按钮mRightButton、中间标题栏mTitleView。
通过动态添加控件的方式使用addView()方法将三个控件加入到定义的TopBar模板中并给它们设置我们前面所获取到的具体的属性值比如标题的文字、颜色、大小等代码如下所示。
private TextView mTitleView;
private Button mLeftButton;
private Button mRightButton;private RelativeLayout.LayoutParams mLeftParams;
private RelativeLayout.LayoutParams mRightParams;
private RelativeLayout.LayoutParams mTitleParams;private void initView(Context context) {mTitleView new TextView(context);mLeftButton new Button(context);mRightButton new Button(context);// 为创建的组件赋值值就来源于引用的xml文件中给对应属性的赋值mTitleView.setText(mTitleText);mTitleView.setTextSize(mTitleTextSize);mTitleView.setTextColor(mTitleTextColor);mTitleView.setGravity(Gravity.CENTER);mLeftButton.setText(mLeftText);mLeftButton.setTextColor(mLeftTextColor);mLeftButton.setBackgroundDrawable(mLeftBackground);mLeftButton.setTextSize(mLeftTextSize);mRightButton.setText(mRightText);mRightButton.setTextSize(mRightTextSize);mRightButton.setBackgroundDrawable(mRightBackground);mRightButton.setTextColor(mRightTextColor);// 为组件元素设置相应的布局元素// 设置布局的layout_width和layout_height属性mLeftParams new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);// 该方法表示所设置节点的属性必须关联其他兄弟节点或者属性值为布尔值。mLeftParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT, RelativeLayout.TRUE);// 动态添加组件addView(mLeftButton, mLeftParams);mRightParams new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);mRightParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, RelativeLayout.TRUE);addView(mRightButton, mRightParams);mTitleParams new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);mTitleParams.addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE);addView(mTitleView, mTitleParams);
}
作为UI模板调用者所需要这些按钮的实现功能都是不一样的。
因此不能直接在UI模板里实现逻辑可以通过接口回调的思想实现逻辑交给调用者。 定义接口。定义一个左右按钮点击的接口并创建两个方法分别用于左右两个按钮的点击 // 在类内部定义一个接口对象实现回调机制不用去考虑如何实现具体实现由调用者去创建
public interface OnClickListener{// 左按钮点击事件void leftClick();// 右按钮点击事件void rightClick();
}暴露接口给调用者。在模板方法中为左右按键增加点击事件但不实现具体逻辑而是调用接口中相应的点击方法 // 创建一个接口对象
private OnClickListener mListener;// 暴露一个方法给调用者来注册接口通过接口来获得回调者对接口方法的实现
public void setOnClickListener(OnClickListener listener) {this.mListener listener;
}private void initEvent(){// 按钮的点击事件不需要具体的实现只需要调用接口方法回调的时候会有具体实现mLeftButton.setOnClickListener(new OnClickListener() {Overridepublic void onClick(View v) {mListener.leftClick();}});mRightButton.setOnClickListener(new OnClickListener() {Overridepublic void onClick(View v) {mListener.rightClick();}});
} 实现接口回调。调用者的代码实现这样接口确定具体的实现逻辑并使用第二步中暴露的方法将接口的对象传递进去从而完成回调。通常情况下可以使用匿名内部类的形式来实现接口中的方法 mTopBar.setOnClickListener(new TopBar.OnClickListener() {Overridepublic void leftClick() {// 点击左边按钮}Overridepublic void rightClick() {// 点击右边按钮}
} 除了通过接口回调的方式来实现动态的控制UI模板同样可以使用公共方法来动态地修改UI模板中的UI进一步提高了模板的可定制性 public static final int LEFT 1;
public static final int RIGHT 2;/*** 设置按钮的显示与否通过常量区分visible区分是否显示** param view 标记View* param visible 是否显示* /
public void setVisable(int view, int visible){switch(view) {case LEFT:mLeftButton.setVisibility(visible);break;case RIGHT:mRightButton.setVisibility(visible);break;}
}通过如上代码调用者通过TopBar对象调用这个方法后根据参数可以动态地控制按钮的显示 // 控制TopBar上组件的状态
mTopBar.setVisable(TopBar.LEFT, View.VISIBLE);
mTopBar.setVisable(TopBar.RIGHT, View.GONE);
3 引用UI模板
在引用前需要指定引用第三方控件的名字空间
xmlns:androidhttp://schemas.android.com/apk/res/android!--
这里指定了名字空间为“android”因此在接下来使用系统属性时才可以使用“android:”来引用Android的系统属性。
同样如果要使用自定义属性那么就需要创建自己的名字空间。
在Android Stuido中第三方控件都使用如下代码来引用名字空间。
xmlns:apphttp://schemas.android.com/apk/res/res-auto (将引入的第三方控件的名字空间取名为app)在XML文件中使用自定义的属性时就可以通过这个名字空间来引用如下
--com.example.demo.TopBarandroid:idid/tbandroid:layout_widthmatch_parentandroid:layout_height100dpandroid:layout_alignParentBottomtrueapp:leftBackGround#ff000000app:leftTextBackapp:leftTextColor#ffff6734app:leftTextSize25dp app:rightTextMoreapp:rightTextSize25dpapp:rightTextColor#ff123456app:title自定义标题app:titleTextColor#ff654321/使用自定义的View与系统原生的View最大的区别就是在声明控件时需要指定完整的包名而在引用自定义的属性时需要使用自定义的xmlns名字。
三、重写View来实现全新控件 Android系统原生控件无法满足我们的需求的时候我们就可以完全创建一个新的自定义View来实现需要的功能。 创建一个自定义View难点在于绘制控件和实现交互这也是评价一个自定义View优劣的标准之一需要继承View类并重写它的onDraw()、onMeasure()等方法来实现绘制逻辑(可选) 重写onTouchEvent()等触控事件来实现交互逻辑(可选) 引入自定义属性丰富自定义View的可定制性
1 弧线展示图
实例 1.1 具体步骤
1绘制中间的圆形
2绘制圆形中间的文字
3绘制圆形外面的圆弧、外圈的弧线
public class ScaleMap extends View {private int mMeasureHeigth;// 控件高度private int mMeasureWidth;// 控件宽度// 圆形private Paint mCirclePaint;private float mCircleXY;//圆心坐标private float mRadius;//圆形半径// 圆弧private Paint mArcPaint;private RectF mArcRectF;//圆弧的外切矩形private float mSweepAngle;//圆弧的角度private float mSweepValue;// 文字private Paint mTextPaint;private String mShowText;//文本内容private float mShowTextSize;//文本大小public ScaleMap(Context context) {this(context, null);}public ScaleMap(Context context, Nullable AttributeSet attrs) {this(context, attrs, 0);}public ScaleMap(Context context, Nullable AttributeSet attrs, int defStyleAttr) {this(context, attrs, defStyleAttr, 0);}// 如果不用后面的参数就不需要重构后面的直接将其内容写在第一个构造方法就可以父类会自动执行后面的构造方法public ScaleMap(Context context, Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {super(context, attrs, defStyleAttr, defStyleRes);// 初始化操作 }Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {mMeasureWidth MeasureSpec.getSize(widthMeasureSpec);//获取控件宽度mMeasureHeigth MeasureSpec.getSize(heightMeasureSpec);//获取控件高度setMeasuredDimension(mMeasureWidth, mMeasureHeigth);initPaint(); // 画笔中用到了宽高所以在此初始化画笔}/*** 准备画笔-- 在初始化的时候设置好绘制三种图形的参数*/private void initPaint() {float length Math.min(mMeasureWidth,mMeasureHeigth);// 圆的代码mCircleXY length / 2;// 确定圆心坐标mRadius (float) (length * 0.5 / 2);// 确定半径mCirclePaint new Paint();mCirclePaint.setAntiAlias(true);// 去锯齿mCirclePaint.setColor(getResources().getColor(android.R.color.holo_green_dark));// 弧线需要 指定其椭圆的外接矩形// 矩形mArcRectF new RectF((float) (length * 0.1), (float) (length * 0.1), (float)(length * 0.9),(float) (length * 0.9));mSweepAngle (mSweepValue / 100f) * 360f;mArcPaint new Paint();mArcPaint.setColor(getResources().getColor(android.R.color.holo_blue_bright));mArcPaint.setStrokeWidth((float) (length * 0.1));//圆弧宽度mArcPaint.setStyle(Style.STROKE);//圆弧// 文字只需要设置好文字的起始绘制位置即可mShowText Android Skill;mShowTextSize 50;mTextPaint new Paint();mTextPaint.setTextSize(mShowTextSize);mTextPaint.setTextAlign(Paint.Align.CENTER);}Overrideprotected void onDraw(Canvas canvas) { // -- 在onDraw()方法中去绘制super.onDraw(canvas);// 绘制圆canvas.drawCircle(mCircleXY, mCircleXY, mRadius, mCirclePaint);// 绘制圆弧逆时针绘制角度跟canvas.drawArc(mArcRectF, 90, mSweepAngle, false, mArcPaint);// 绘制文字canvas.drawText(mShowText, 0, mShowText.length(), mCircleXY, mCircleXY mShowTextSize / 4, mTextPaint);}// 让调用者来设置不同的状态值使弧形弧度变化public void setSweepValue(float sweepValue) {if (sweepValue ! 0) {mSweepValue sweepValue;} else {mSweepValue 25;}// 这个方法可以刷新UI -- 在修改UI后通过调用this.invalidate()方法来实现UI的重绘this.invalidate();}}
2 音频条形图
实例 2.1 具体步骤
1基本思路绘制一个个的矩形每个矩形之间稍微偏移一点距离即可
动态效果实现思路在onDraw()方法中调用invalidate()方法通知View进行重绘
-- 问题这样直接重绘的速度太快影响观感效果体验因此可以适当进行延时通知重绘
使用postInvalidateDelayed(300/*ms*/)来进行延时重绘
this.invalidate();
this.postInvalidateDelayed(300);2矩形渐变效果
思路给绘制的Paint对象可以增加一个LinearGradient渐变效果
private int mWidth;//控件的宽度
private int mRectWidth;// 矩形的宽度
private int mRectHeight;// 矩形的高度
private Paint mPaint;
private int mRectCount;// 矩形的个数
private int offset 5;// 偏移
private double mRandom;
private LinearGradient lg;// 渐变public ScaleMap(Context context) {this(context, null);
}public ScaleMap(Context context, Nullable AttributeSet attrs) {super(context, attrs);initPaint(); // 这些要在这里设置因为渐变效果
}Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {// 设置宽高setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.getSize(heightMeasureSpec));
}// 初始化画笔
private void initPaint() {mPaint new Paint();mPaint.setColor(Color.GREEN);mPaint.setStyle(Paint.Style.FILL);mRectCount 12;
}//重写onDraw方法
Override
protected void onDraw(Canvas canvas) {super.onDraw(canvas);for (int i 0; i mRectCount; i) {mRandom Math.random();float currentHeight (int) (mRectHeight * mRandom);canvas.drawRect((float) (mWidth * 0.4 / 2 mRectWidth * i offset * i), currentHeight,(float) (mWidth * 0.4 / 2 mRectWidth * (i 1) offset * i), mRectHeight, mPaint);}postInvalidateDelayed(300);
}//重写onSizeChanged方法给画笔加上渐变
Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {super.onSizeChanged(w, h, oldw, oldh);mWidth getWidth();mRectHeight getHeight();mRectWidth (int) (mWidth * 0.6 / mRectCount);lg new LinearGradient(0, 0, mRectWidth, mRectHeight, Color.GREEN, Color.BLUE, TileMode.CLAMP);mPaint.setShader(lg);
}
四、补充自定义ViewGroup
TODO