自定义View
给我们提供了常用组件,然而随着开发的深入,这些组件渐渐无法满足我们各式各样的需求,此时就需要我们在已有的组件上创建新的功能,甚至是直接自己写一个新的View控件,来满足自己的需要。这就是我们常说的自定义View。
在自定义View时候,我们常常会重写onDraw()方法来重新绘制我们的控件;当该控件需要用wrap_content属性时候,还需要用到onMearsure()方法来重新测量;以及需要一些特殊样式时候,还可以通过修改attrs.xml(或者写其他xml)来设置控件属性。
在View中一些重要的回调方法有:
():加载完XML组件后回调。
():当组件大小变化时回调。
():通过回调该方法进行控件测量。
():通过回调该方法确定控件显示的位置。
():监听到触摸事件后回调。
通常情况下,我们用三种方法来实现自定义的控件:
l 对现有控件的扩展;
l 通过组合方式实现新的控件;
通过重写View实现新的控件。
1.对现有控件的扩展
对现有控件的扩展指的是在Android原生控件的基础上进行一些功能扩展。一般情况下都是在进行重写onDraw方法来实现。
以TextView为例,我们想在写的字下面加上两层背景,再让字实现闪烁滚动的效果。此时就可以先自定义View继承TextView类,然后可以重写onDraw()的方法。
super.onDraw()方法前,而将字体闪烁设置写在super.onDraw()方法后。如下所示:
@Override
protected void onDraw(Canvas canvas) {
canvas.drawRect(0,0,getMeasuredWidth()+10,getMeasuredHeight()+10,mPaint1);
canvas.drawRect(10,10,getMeasuredWidth()-10,getMeasuredHeight()-10,mPaint2);
canvas.save();
canvas.translate(10,0);
super.onDraw(canvas);
canvas.restore();
if(matrix!=null){
mTranslate+=mViewwidth/5;
if(mTranslate>2*mViewwidth){
mTranslate=-mViewwidth;
}
matrix.setTranslate(mTranslate,0);
linearGradient.setLocalMatrix(matrix);
postInvalidateDelayed(100);
}
}
完整的代码为:
public class TestTextView extends TextView {
private Paint mPaint;
private Paint mPaint1;
private Paint mPaint2;
private int mTranslate;
private int mViewwidth=0;
private Matrix matrix;
private LinearGradient linearGradient;
public TestTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
public TestTextView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public TestTextView(Context context) {
super(context);
init(context);
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawRect(0,0,getMeasuredWidth()+10,getMeasuredHeight()+10,mPaint1);
canvas.drawRect(10,10,getMeasuredWidth()-10,getMeasuredHeight()-10,mPaint2);
canvas.save();
canvas.translate(10,0);
super.onDraw(canvas);
canvas.restore();
if(matrix!=null){
mTranslate+=mViewwidth/5;
if(mTranslate>2*mViewwidth){
mTranslate=-mViewwidth;
}
matrix.setTranslate(mTranslate,0);
linearGradient.setLocalMatrix(matrix);
postInvalidateDelayed(100);
}
}
@Override
protected void onSizeChanged(int w,int h,int oldw,int oldh){
super.onSizeChanged(w, h, oldw, oldh);
if(mViewwidth==0){
mViewwidth=getMeasuredWidth();
if(mViewwidth>0){
mPaint=getPaint();
linearGradient=new LinearGradient(0,0,mViewwidth,0,new int[]{Color.BLUE,0xffffffff,Color.BLUE},null, Shader.TileMode.CLAMP);
mPaint.setShader(linearGradient);
matrix=new Matrix();
}
}
}
private void init(Context context) {
mPaint1=new Paint();
mPaint1.setColor(Color.BLUE);
mPaint2=new Paint();
mPaint2.setColor(Color.RED);
}
}
运行一下看看效果为:
2.通过组合方式实现新的控件
这种方式的自定义View是非常常见的,通常集成与一个合适的ViewGroup,比如LinearLayout,RelativeLayout等各种布局。然后将各种控件组合在里面,形成一个新的控件,可以供多个布局复用,也可以通过这样的方式将一个大的布局拆分为多块,便于管理。
LinearLayout,布局文件里布置好各种控件,如下所示:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:id="@+id/rl_title"
android:layout_width="match_parent"
android:layout_height="60dp"
android:background="@color/blue">
<TextView
android:id="@+id/tv_title_areacompareform"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginLeft="@dimen/sm_10"
android:gravity="center_vertical"
android:textColor="@color/white"
android:text="标题"/>
<TextView
android:id="@+id/tv_unit"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentRight="true"
android:layout_marginRight="@dimen/sm_20"
android:gravity="center"
android:text="单位:%"
android:textColor="@color/white" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/rl_linechartview"
android:layout_below="@+id/rl_title"
android:layout_width="match_parent"
android:layout_height="match_parent">
<lecho.lib.hellocharts.view.LineChartView
android:id="@+id/lcv_gdl"
android:layout_width="match_parent"
android:layout_marginTop="40dp"
android:layout_marginBottom="30dp"
android:layout_marginLeft="10dp"
android:layout_marginRight="20dp"
android:paddingLeft="5dp"
android:paddingRight="25dp"
android:paddingBottom="20dp"
android:layout_height="match_parent"></lecho.lib.hellocharts.view.LineChartView>
</RelativeLayout>
</RelativeLayout>
然后在自定义View里填充并给这个控件里的各个子控件赋值,代码如下:
public GDLLineChartView(Context context, String title, List<GDLCompareBean> beanList) {
super(context);
this.context = context;
view = LayoutInflater.from(context).inflate(R.layout.view_gdl_lcv, this);
ButterKnife.bind(this, view);
tvTitle.setText(title);
// tvFormTitle.setText(title);
initForm(beanList);
}
private void initForm( List<GDLCompareBean> beanList){
List<Line> lines = new ArrayList<Line>();
List<PointValue> values = new ArrayList<PointValue>();
List<AxisValue> axisValues = new ArrayList<AxisValue>();
for (int j = 0; j < beanList.size(); ++j) {
values.add(new PointValue(j, Float.valueOf(beanList.get(j).getGdl())));
axisValues.add(new AxisValue(j).setLabel(beanList.get(j).getYear()));//添加X轴显示的刻度值
}
Line line = new Line(values);
LineChartValueFormatter formatter=new SimpleLineChartValueFormatter(2);
line.setFormatter(formatter);
line.setColor(0xFF0088a8);
line.setShape(shape);
line.setStrokeWidth(5);//设置折线宽度
line.setFilled(false);//设置折线覆盖区域颜色
line.setPointColor(Color.RED);//设置节点颜色
line.setPointRadius(5);//设置节点半径
line.setHasLabels(true);//是否显示节点数据
line.setHasLines(true);//是否显示折线
lines.add(line);
data = new LineChartData(lines);
data.setAxisXBottom(new Axis(axisValues).setHasLines(true));
data.setAxisYLeft(new Axis().setHasLines(true).setMaxLabelChars(3));
data.setBaseValue(20);//设置反向覆盖区域颜色
data.setValueLabelBackgroundAuto(false);//设置数据背景是否跟随节点颜色
data.setValueLabelBackgroundEnabled(false);//设置是否有数据背景
data.setValueLabelsTextColor(Color.RED);//设置数据文字颜色
data.setValueLabelTextSize(12);//设置数据文字大小
data.setValueLabelTypeface(Typeface.MONOSPACE);//设置数据文字样式
lcvGDL.setLineChartData(data);
lcvGDL.setOnValueTouchListener(new LineChartOnValueSelectListener() {
@Override
public void onValueSelected(int lineIndex, int pointIndex, PointValue value) {
Log.i("sss","sss");
if(gdlLineChartViewSelectedListener!=null){
gdlLineChartViewSelectedListener.onSelected((int)value.getX());
}
}
@Override
public void onValueDeselected() {
Log.i("sss","sss");
}
});
}
接下来暴露接口,以便调用者可以调用接口中相应的点击方法:
public void setOnSelectedListener(GDLLineChartViewSelectedListener gdlLineChartViewSelectedListener){
this.gdlLineChartViewSelectedListener=gdlLineChartViewSelectedListener;
}
public interface GDLLineChartViewSelectedListener{
void onSelected(int position);
}
调用时候只需如此调用即可:
GDLLineChartView lview = new GDLLineChartView(getContext(), MapTitle, gdlCompareBeanList);
llLineChartView.addView(lview);
lview.setOnSelectedListener(new GDLLineChartView.GDLLineChartViewSelectedListener() {
@Override
public void onSelected(int i) {
SingleModuleBean bean = singleModuleBeanList.get(i);
String[] titleList = bean.getTitles().split(",");
String FormTitle = titleList[1];
String Year = bean.getYear() + "年";
FormTitle = Year + FormTitle;
List<GDLBean> gdlBeanList = getList(bean.getTablejson());
GDLAreaFormView aview = new GDLAreaFormView(getContext(), FormTitle, gdlBeanList);
llAreaCompareForm.removeAllViews();
llAreaCompareForm.addView(aview);
}
});
效果图大概为:
3.通过重写View实现新的控件
当有时候需求原生控件里完全没有的控件时候,可以自己创建一个全新的自定义View来。这样的控件继承于View类,通过onDraw()来实现该View的绘制。
以音频图为例子,音频图可以通过绘制一系列的矩形来实现,具体代码如下:
public class VoiceView extends View {
private Paint mPaint;
private int count=10;
private int mRectWidth=60;
private int mRectHeight=800;
private int off=10;
private int mWidth;
public VoiceView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
public VoiceView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public VoiceView(Context context) {
super(context);
init(context);
}
private void init(Context context){
mWidth=getMeasuredWidth();
mPaint=new Paint();
mPaint.setColor(Color.BLUE);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for(int i=0;i<count;i++){
Double random=Math.random();
canvas.drawRect(
(float)(mWidth*0.4/2+mRectWidth*i+off),
(float)(random*mRectHeight),
(float)(mWidth*0.4/2+mRectWidth*i+mRectWidth),
mRectHeight,
mPaint);
}
}
这里我在onDraw()方法里绘制了10个宽度为50,高度为随机的蓝色矩形。然后创建一个activity,添加这个自定义View:
public class MainActivity extends AppCompatActivity {
private VoiceView myView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
myView = new VoiceView(this);//初始化自定义View
this.setContentView(myView);//设置当前的用户界面
}
}
可以看看效果图为: