自定义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);
    }
}

  运行一下看看效果为:

Android 自定义view不走ondraw android自定义view书籍推荐_android

  

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);//设置当前的用户界面
    }
}

  可以看看效果图为:


 

Android 自定义view不走ondraw android自定义view书籍推荐_控件_02