那么让我们来详细讲解一下“Android重写View实现全新的控件”的完整攻略。
什么是重写View
在Android中,View是用户界面的基本构建单元,绝大部分控件都是基于View的,因此我们可以通过重写View来实现我们自定义的控件。
在进行View的重写时,通常需要继承View或者它的子类,然后重写对应的方法。View的子类较多,它们之间的主要区别在于有一些默认的绘制行为和触摸反馈。
实现步骤
- 继承View或者它的子类,并重写对应方法。
扩展自View,通常需要重写onDraw方法。这是一个绘制你自定义View的方法,你需要在该方法中实现自己想要的绘制,例如绘制图像、绘制文字等。
public class MyView extends View {
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onDraw(Canvas canvas) {
// 在这里进行绘制操作
}
}
扩展自ViewGroup,通常需要重写onLayout方法和onMeasure方法。onMeasure方法用于决定目标View的大小,onLayout用于确定它的位置。
public class MyViewGroup extends ViewGroup {
public MyViewGroup(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// 在这里进行子View的布局操作
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 在这里计算目标View的大小
}
}
- 实现一些自定义属性。
在大多数情况下,自定义ViewGroup和自定义View通常需要一些自定义属性。这些属性允许你自定义View的各种方面,例如:颜色、大小、样式等。
可以通过在xml中定义属性,然后在自定义View中进行解析,来实现添加自定义属性的功能。下面是一个简单的例子。
在res/values/attrs.xml中添加以下内容:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MyView">
<attr name="my_attribute" format="string"/>
</declare-styleable>
</resources>
在xml中使用MyView时,可以定义一个my_attribute,例如:
<com.example.MyView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:my_attribute="value"/>
在自定义View类中引入属性并解析:
public class MyView extends View {
private String mAttributeValue;
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.getTheme().obtainStyledAttributes(
attrs,
R.styleable.MyView,
0, 0);
try {
mAttributeValue = a.getString(R.styleable.MyView_my_attribute);
} finally {
a.recycle();
}
}
}
- 处理触摸输入事件。
如果你想让你的自定义View能够响应触摸事件,那么就需要处理它们。你可以通过覆盖onTouchEvent方法来处理输入事件。
public class MyView extends View {
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 处理点击事件
break;
case MotionEvent.ACTION_MOVE:
// 处理移动事件
break;
case MotionEvent.ACTION_UP:
// 处理放开事件
break;
}
return true;
}
}
示例
现在我们来看两个实际的例子,来更好的理解如何重写View创建自定义控件。
自定义圆形进度条
我们来尝试实现一个自定义的圆形进度条控件。实现的主要步骤如下:
- 自定义View来承载该控件。
public class CircleProgressBar extends View {
// ...
}
- 实现属性。
定义自定义控件的一些属性,例如底部圆形颜色、进度圆环颜色、圆环宽度、最大进度值等。
<declare-styleable name="CircleProgressBar">
<attr name="bottomColor" format="color"/>
<attr name="progressColor" format="color"/>
<attr name="strokeWidth" format="dimension"/>
<attr name="maxValue" format="integer"/>
</declare-styleable>
- 重写onDraw方法,代码中包含如何画一个圆环及进度条的描述。
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//绘制背景
canvas.drawArc(oval, startAngle, sweepAngle, false, mPaintBottom);
//绘制进度条进度值
canvas.drawArc(oval, startAngle, progressValue / maxValue * sweepAngle, false, mPaintProgress);
}
- 添加setXXX方法,并在其中进行相应的处理。
public void setProgressValue(int progress) {
if (progress < 0) {
throw new IllegalArgumentException("progress < 0");
}
this.progressValue = progress;
invalidate();
}
自定义流式布局
流式布局是Android中较为受欢迎的布局方式之一,它可以自适应子View的宽高,从而在屏幕上按照一行或多行排列。
实现的主要步骤如下:
- 继承ViewGroup类。
public class FlowLayout extends ViewGroup {
public FlowLayout(Context context) {
super(context);
inflater = LayoutInflater.from(context);
}
public FlowLayout(Context context, AttributeSet attrs) {
super(context, attrs);
inflater = LayoutInflater.from(context);
}
public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
inflater = LayoutInflater.from(context);
}
// ...
}
- 重写onMeasure方法,进行布局的测量和排列。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = MeasureSpec.getSize(widthMeasureSpec);
int lineWidth = 0;
int lineHeight = 0;
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View childView = getChildAt(i);
measureChild(childView, widthMeasureSpec, heightMeasureSpec);
MarginLayoutParams layoutParams = (MarginLayoutParams) childView.getLayoutParams();
int childWidth = childView.getMeasuredWidth() + layoutParams.leftMargin + layoutParams.rightMargin;
int childHeight = childView.getMeasuredHeight() + layoutParams.topMargin + layoutParams.bottomMargin;
if (lineWidth + childWidth > width) {
width = Math.max(width, lineWidth);
lineWidth = childWidth;
height += lineHeight;
lineHeight = childHeight;
} else {
lineWidth += childWidth;
lineHeight = Math.max(lineHeight, childHeight);
}
if (i == childCount - 1) {
width = Math.max(width, lineWidth);
height += lineHeight;
}
}
setMeasuredDimension(width, height);
}
- 重写onLayout方法,对子View进行排列。
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
mAllViews.clear();
mLineHeights.clear();
int width = getWidth();
int lineWidth = 0;
int lineHeight = 0;
List<View> lineViews = new ArrayList<>();
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View childView = getChildAt(i);
MarginLayoutParams layoutParams = (MarginLayoutParams) childView.getLayoutParams();
int childWidth = childView.getMeasuredWidth() + layoutParams.leftMargin + layoutParams.rightMargin;
int childHeight = childView.getMeasuredHeight() + layoutParams.topMargin + layoutParams.bottomMargin;
if (lineWidth + childWidth > width) {
mLineHeights.add(lineHeight);
mAllViews.add(lineViews);
lineWidth = 0;
lineHeight = childHeight;
lineViews = new ArrayList<>();
}
lineWidth += childWidth;
lineHeight = Math.max(lineHeight, childHeight);
lineViews.add(childView);
}
mLineHeights.add(lineHeight);
mAllViews.add(lineViews);
int left = 0;
int top = 0;
int lineCount = mAllViews.size();
for (int i = 0; i < lineCount; i++) {
lineViews = mAllViews.get(i);
lineHeight = mLineHeights.get(i);
for (int j = 0; j < lineViews.size(); j++) {
View childView = lineViews.get(j);
if (childView.getVisibility() == View.GONE) {
continue;
}
MarginLayoutParams layoutParams = (MarginLayoutParams) childView.getLayoutParams();
int childWidth = childView.getMeasuredWidth();
int childHeight = childView.getMeasuredHeight();
int lc = left + layoutParams.leftMargin;
int tc = top + layoutParams.topMargin;
int rc = lc + childWidth;
int bc = tc + childHeight;
childView.layout(lc, tc, rc, bc);
left += layoutParams.leftMargin + childWidth + layoutParams.rightMargin;
}
left = 0;
top += lineHeight;
}
}
- 添加removeAllViews和addView方法。
public void removeAllViews(boolean isRemoveHead) {
if (isRemoveHead) {
try {
for (int i = getChildCount() - 1; i >= 0; i--) {
removeViewAt(i);
}
} catch (Exception e) {
e.printStackTrace();
}
} else {
removeAllViews();
}
}
public void addChildView(View child, boolean isNewline) {
addView(child, childLayoutParams(isNewline));
}
结语
以上是重写View实现全新的控件的攻略,可以通过这些步骤和例子来实现更多的自定义控件,如果你想深入学习Android自定义控件,可以学习更多的基本知识,例如如何绘制Path和Canvas、如何处理手势操作等等。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Android重写View实现全新的控件 - Python技术站