2)继承View,重写onDraw 方法
3)继承ViewGroup 派生特殊的layout,通过组合来实现新的控件
4)继承特定的ViewGroup
自定义View 注意事项:
@Override protected void onDraw(Canvas canvas) { //在回调父类方法前,实现自己的逻辑,这里即在绘制文本内容前 super.onDraw(canvas);//调用父类的方法,实现原生控件的功能 //在回调父类方法前,实现自己的逻辑,这里即在绘制文本内容后 }
public class MyTextView extends TextView { private Paint paint1; private Paint paint2; public MyTextView(Context context) { super(context); initView(); } public MyTextView(Context context, AttributeSet attrs) { super(context, attrs); initView(); } public MyTextView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initView(); } private void initView() { paint1 = new Paint(); paint1.setColor(getResources().getColor(android.R.color.holo_blue_light)); paint1.setStyle(Paint.Style.FILL); paint2 = new Paint(); paint2.setColor(Color.YELLOW); paint2.setStyle(Paint.Style.FILL); } @Override protected void onDraw(Canvas canvas) { //在回调父类方法前,实现自己的逻辑,这里即在绘制文本内容前 canvas.drawRect(0,// 绘制外层矩形 0, getMeasuredWidth(), getMeasuredHeight(), paint1); // 绘制内层矩形 canvas.drawRect(10, 10, getMeasuredWidth()-20, getMeasuredHeight()-20, paint2); canvas.save();//用来保存Canvas的状态。save之后,可以调用Canvas的平移、放缩、旋转、裁剪等操作。 // 绘制文字前平移10像素 canvas.translate(50,0); // 父类完成的方法,即绘制文本 super.onDraw(canvas);//调用父类的方法,实现原生控件的功能 canvas.restore();//用来恢复Canvas之前保存的状态。防止save后对Canvas执行的操作对后续的绘制有影响。 //在回调父类方法前,实现自己的逻辑,这里即在绘制文本内容后 } }
1.2给 Paint 增加渐变渲染器
package com.androidheroes.myview.ui; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.LinearGradient; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Shader; import android.util.AttributeSet; import android.widget.TextView; /** * @项目名 MyView * @包名 com.androidheroes.myview * @时间 十二月 * @描述 TODO * Created by Administrator on 2016/12/15. */ public class ShineTextView extends TextView { private int mViewWidth = 0; private int mTranslate = 0; private Paint paint; private LinearGradient linearGradient; private Matrix matrix; public ShineTextView(Context context) { super(context); } public ShineTextView(Context context, AttributeSet attrs) { super(context, attrs); } public ShineTextView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @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) { paint = getPaint(); linearGradient = new LinearGradient( 0,//渐变的起始点x坐标; 0,//渐变的起始点y坐标 mViewWidth,//渐变的终点x坐标; 0,//渐变的终点y坐标 new int[]{ Color.BLUE, 0xffffffff, Color.BLUE},//参数colors表示渐变的颜色数组 null,//参数positions用来指定颜色数组的相对位置 Shader.TileMode.CLAMP);//参数tile表示平铺方式。 paint.setShader(linearGradient); matrix = new Matrix(); } } } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (matrix != null) { mTranslate += mViewWidth / 5; if (mTranslate > 2 * mViewWidth) { mTranslate = -mViewWidth; } matrix.setTranslate(mTranslate,0); linearGradient.setLocalMatrix(matrix); postInvalidateDelayed(100); } } }
Shader.TileMode有3种参数可供选择,分别为CLAMP、REPEAT和MIRROR:
CLAMP的作用是如果渲染器超出原始边界范围,则会复制边缘颜色对超出范围的区域进行着色
REPEAT的作用是在横向和纵向上以平铺的形式重复渲染位图
MIRROR的作用是在横向和纵向上以镜像的方式重复渲染位图
首先我们先来onSizeChanged()里面的代码,在这段代码中主要是定义了LinearGradient:
linearGradient = new LinearGradient( 0,//渐变的起始点x坐标; 0,//渐变的起始点y坐标 mViewWidth,//渐变的终点x坐标; 0,//渐变的终点y坐标 new int[]{ Color.BLUE, 0xffffffff, Color.BLUE},//参数colors表示渐变的颜色数组 null,//参数positions用来指定颜色数组的相对位置 Shader.TileMode.CLAMP);//参数tile表示平铺方式。这 段代码可以这么理解,它定义了一组渐变的数值是{ 0x33ffffff, 0xffffffff, 0x33ffffff },这个渐变的初始位置是在手机屏幕的(0,0)位置,
package com.example.yhadmin.myview.ui; /* * @项目名: MyView * @包名: com.example.yhadmin.myview.ui * @文件名: MyCicler * @创建者: YHAdmin * @创建时间: 2018/5/9 17:22 * @描述: TODO */ import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.support.annotation.Nullable; import android.util.AttributeSet; import android.view.View; import com.example.yhadmin.myview.R; public class MyCircle extends View { private int mColor = Color.RED; private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); //默认大小 private int mWidth = 200; private int mHeigth = 200; public MyCircle(Context context) { super(context); init(); } // public MyCircle(Context context, @Nullable AttributeSet attrs) { this(context, attrs,0); } public MyCircle(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { mPaint.setColor(mColor); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); int width = getWidth() ; int height = getHeight() ; int radius = Math.min(width, height) / 2; canvas.drawCircle(width / 2, height / 2, radius, mPaint); } }
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:background="#ffffff" tools:context="com.example.yhadmin.myview.MainActivity"> <com.example.yhadmin.myview.ui.MyCicler android:layout_width="match_parent" android:layout_height="100dp" android:background="#000000" /> </LinearLayout>运行效果如图1 所示,符合我们的预期
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:background="#ffffff" tools:context="com.example.yhadmin.myview.MainActivity"> <com.example.yhadmin.myview.ui.MyCicler android:layout_width="match_parent" android:layout_height="100dp" android:background="#000000" android:layout_margin="20dp" /> </LinearLayout>
运行效果如图2所示,符合我们的预期
设置20dp padding 后,布局代码如下所示:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:background="#ffffff" tools:context="com.example.yhadmin.myview.MainActivity"> <com.example.yhadmin.myview.ui.MyCicler android:layout_width="match_parent" android:layout_height="100dp" android:background="#000000" android:padding="20dp" android:layout_margin="20dp" /> </LinearLayout>
运行效果如图3所示,从图片中我们可以发现,padding 根本没有生效
为其宽设置 wrap_content 后,布局代码如下所示:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:background="#ffffff" tools:context="com.example.yhadmin.myview.MainActivity"> <com.example.yhadmin.myview.ui.MyCicler android:layout_width="wrap_content" android:layout_height="100dp" android:background="#000000" android:padding="20dp" android:layout_margin="20dp" /> </LinearLayout>
运行效果如图4所示,从图片中我们可以发现,wrap_content根本没有生效,效果相当于match_parent
//默认大小 private int mWidth = 200; private int mHeigth = 200; @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension(mWidth, mHeigth); } else if (widthSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension(mWidth, heightSpecSize); } else if (heightSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension(widthSpecSize, mHeigth); } }
onDraw
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); int paddingLeft = getPaddingLeft(); int paddingRight = getPaddingRight(); int paddingTop = getPaddingTop(); int paddingBottom = getPaddingBottom(); int width = getWidth() -paddingLeft -paddingRight ; int height = getHeight() - paddingTop - paddingBottom; int radius = Math.min(width, height) / 2; canvas.drawCircle(paddingLeft+width / 2, paddingTop+height / 2, radius, mPaint); }
运行效果如图5所示,可以发现 padding 和wrap_content 均已生效
添加自定义属性,在Value 目录下新建attrs.xml 文件,并在其中声明一个 CircleView 属性集合
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="MyCircle"> <attr name="circle_cocler" format="color|reference"/> </declare-styleable> </resources>
在View 的构造方法中解析自定义属性的值,并做相应处理,完整代码如下:
package com.example.yhadmin.myview.ui; /* * @项目名: MyView * @包名: com.example.yhadmin.myview.ui * @文件名: MyCicler * @创建者: YHAdmin * @创建时间: 2018/5/9 17:22 * @描述: TODO */ import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.support.annotation.Nullable; import android.util.AttributeSet; import android.view.View; import com.example.yhadmin.myview.R; public class MyCircle extends View { private int mColor = Color.RED; private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); //默认大小 private int mWidth = 200; private int mHeigth = 200; public MyCircle(Context context) { super(context); init(); } // public MyCircle(Context context, @Nullable AttributeSet attrs) { this(context, attrs,0); } public MyCircle(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.MyCircle); mColor = ta.getColor(R.styleable.MyCircle_circle_cocler, Color.RED); ta.recycle(); init(); } private void init() { mPaint.setColor(mColor); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension(mWidth, mHeigth); } else if (widthSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension(mWidth, heightSpecSize); } else if (heightSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension(widthSpecSize, mHeigth); } } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); int paddingLeft = getPaddingLeft(); int paddingRight = getPaddingRight(); int paddingTop = getPaddingTop(); int paddingBottom = getPaddingBottom(); int width = getWidth() -paddingLeft -paddingRight ; int height = getHeight() - paddingTop - paddingBottom; int radius = Math.min(width, height) / 2; canvas.drawCircle(paddingLeft+width / 2, paddingTop+height / 2, radius, mPaint); } }
布局代码如下:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#ffffff" android:orientation="vertical" tools:context="com.example.yhadmin.myview.MainActivity"> <com.example.yhadmin.myview.ui.MyCircle android:layout_width="wrap_content" android:layout_height="100dp" android:layout_margin="20dp" android:background="#000000" android:padding="20dp" app:circle_cocler="@color/light_green" /> </LinearLayout>
<?xml version="1.0" encoding="utf-8"?> <resources> <!--TopBar 的自定义属性 reference 表示可引用 dimension表示尺寸 attr 属性名称 format 属性类型 |分隔不同的属性 declare-styleable 指定控件的名称--> <declare-styleable name="TopBar"> <attr name="title" format="string" /> <attr name="titleTextSize" format="dimension" /><!--name 属性引用的名称 --> <attr name="titleTextColor" format="color" /> <attr name="leftTextColor" format="color" /> <attr name="leftBackground" format="reference|color" /> <attr name="leftText" format="string" /> <attr name="rightTextColor" format="color" /> <attr name="rightBackground" format="reference|color" /> <attr name="rightText" format="string" /> </declare-styleable> </resources>
继承RelativeLayout 的 TopBar 控件 ,代码如下:
package com.androidheroes.myview.ui; import android.content.Context; import android.content.res.TypedArray; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.View; import android.widget.Button; import android.widget.RelativeLayout; import android.widget.TextView; import com.androidheroes.myview.R; /** * @项目名 MyView * @包名 com.androidheroes.myview.ui * @时间 十二月 * @描述 自定义组合控件 * Created by Administrator on 2016/12/16. */ public class TopBar extends RelativeLayout { // 左按钮的属性值,即我们在atts.xml文件中定义的属性 private int mLeftTextColor; private Drawable mLeftBackground; private String mLeftText; // 右按钮的属性值,即我们在atts.xml文件中定义的属性 private int mRightTextColor; private Drawable mRightBackground; private String mRightText; // 标题的属性值,即我们在atts.xml文件中定义的属性 private float mTitleTextSize; private int mTitleTextColor; private String mTitle; //创建新的组件元素 private Button mLeftButton; private Button mRightButton; private TextView mTitleView; // 布局属性,用来控制组件元素在ViewGroup中的位置 private LayoutParams mLeftPapams, mRightParams,mTitleParams; // 映射传入的接口对象 private topbarClickListener mListener; public TopBar(Context context) { super(context); } public TopBar(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } public TopBar(Context context, AttributeSet attrs) { super(context, attrs); // 设置topbar的背景 setBackgroundColor(0xFFF59563); // 通过这个方法,将你在atts.xml中定义的declare-styleable属性集合中 // 的所有属性的值存储到TypedArray中 TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.TopBar); // 左按钮的属性值,即我们在atts.xml文件中定义的属性 mLeftTextColor = ta.getColor(R.styleable.TopBar_leftTextColor, 0); mLeftBackground = ta.getDrawable(R.styleable.TopBar_leftBackground); mLeftText = ta.getString(R.styleable.TopBar_leftText); // 右按钮的属性值,即我们在atts.xml文件中定义的属性 mRightTextColor = ta.getColor(R.styleable.TopBar_rightBackground, 0); mRightBackground = ta.getDrawable(R.styleable.TopBar_rightBackground); mRightText = ta.getString(R.styleable.TopBar_rightText); // 标题的属性值,即我们在atts.xml文件中定义的属性 mTitleTextSize = ta.getDimension(R.styleable.TopBar_titleTextSize, 10); mTitleTextColor = ta.getColor(R.styleable.TopBar_titleTextColor, 0); mTitle = ta.getString(R.styleable.TopBar_title); //获取完TypedArray的值后,一般要调用ecyle方法释放资源,避免重新创建的时候的错误 ta.recycle(); //创建新的组件元素 mLeftButton = new Button(context); mRightButton = new Button(context); mTitleView = new TextView(context); // 为创建的组件元素赋值 // 值就来源于我们在引用的xml文件中给对应属性的赋值 mLeftButton.setBackground(mLeftBackground); mLeftButton.setText(mLeftText); mLeftButton.setTextColor(mLeftTextColor); mRightButton.setBackground(mRightBackground); mRightButton.setText(mRightText); mRightButton.setTextColor(mRightTextColor); mTitleView.setText(mTitle); mTitleView.setTextSize(mTitleTextSize); mTitleView.setTextColor(mTitleTextColor); // 为组件元素设置相应的布局元素 mLeftPapams = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT); mLeftPapams.addRule(RelativeLayout.ALIGN_PARENT_LEFT,TRUE); // 添加到ViewGroup addView(mLeftButton,mLeftPapams); mRightParams = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT); mRightParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT,TRUE); addView(mRightButton,mRightParams); mTitleParams = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT); mTitleParams.addRule(RelativeLayout.CENTER_IN_PARENT,TRUE); addView(mTitleView,mTitleParams); // 按钮的点击事件,不需要具体的实现, // 只需调用接口的方法,回调的时候,会有具体的实现 mRightButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { mListener.rightClick(); } }); mLeftButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { mListener.leftClick(); } }); } // 暴露一个方法给调用者来注册接口回调,方法参数即借口对象 // 通过接口来获得回调者对接口方法的实现 public void setOnTopbarClickListener(topbarClickListener listener){ this.mListener = listener; } // 接口对象,实现回调机制, // 在回调方法中通过映射的接口对象调用接口中的方法 // 而不用去考虑如何实现,具体的实现由调用者去创建 public interface topbarClickListener{ // 左按钮点击事件 void leftClick(); // 右按钮点击事件 void rightClick(); } /** * 设置按钮的显示与否 通过id区分按钮,flag区分是否显示 * @param id 控件id * @param flag 是否显示 */ public void setButtonVisable(int id,boolean flag){ if (flag){ if (id==0){ mLeftButton.setVisibility(VISIBLE); }else { mRightButton.setVisibility(VISIBLE); } }else { if (id==0){ mLeftButton.setVisibility(GONE); }else { mRightButton.setVisibility(GONE); } } } }
@Override protected void onLayout(boolean changed, int l, int t, int r, int b) { // The layout has actually already been performed and the positions // cached. Apply the cached values to the children. final int count = getChildCount(); for (int i = 0; i < count; i++) { View child = getChildAt(i); if (child.getVisibility() != GONE) { RelativeLayout.LayoutParams st = (RelativeLayout.LayoutParams) child.getLayoutParams(); child.layout(st.mLeft, st.mTop, st.mRight, st.mBottom); } } }
运用测试
public class TopbarTestActivity extends Activity { private TopBar topbar; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_topbar_test); topbar = (TopBar) findViewById(R.id.topbar); topbar.setOnTopbarClickListener(new TopBar.topbarClickListener() { @Override public void leftClick() { Toast.makeText(TopbarTestActivity.this, "legt", Toast.LENGTH_SHORT) .show(); } @Override public void rightClick() { Toast.makeText(TopbarTestActivity.this, "right", Toast.LENGTH_SHORT) .show(); } }); // 控制topbar上组件的状态 topbar.setButtonVisable(0,true); topbar.setButtonVisable(1,false); } }
引用UI模板 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" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.androidheroes.myview.activity.TopbarTestActivity"> <com.androidheroes.myview.ui.TopBar android:id="@+id/topbar" android:layout_width="match_parent" android:layout_height="40dp" custom:leftBackground="@drawable/blue_bottom" custom:leftText="Back" custom:leftTextColor="#fff" custom:rightBackground="@drawable/blue_bottom" custom:rightText="More" custom:rightTextColor="#fff" custom:title="自定义标题" custom:titleTextColor="#123412" custom:titleTextSize="10sp" /> </RelativeLayout>
在引用前,需要制定引用第三方控件的名字空间,即:
xmlns:android="http://schemas.android.com/apk/res/android"
这行代码指定了引用的名字空间 xmlns,指定命名空间为 “android” 因此在接下来使用系统属性时,才能使用"android:"来引用Android系统的属性,如要使用自定义的属性,就需要创建自己的名字空间,在Android Studio 中,第三方的控件都使用如下代码来引入名字空间:
xmlns:custom="http://schemas.android.com/apk/res-auto"
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="TopBar"> <attr name="title" format="string"/> <attr name="titleTextSize" format="dimension"/> <attr name="titleTextColor" format="color"/> <attr name="leftTextColor" format="color"/> <attr name="leftBackground" format="color|reference"/> <attr name="leftText" format="string"/> <attr name="rightTextColor" format="color"/> <attr name="rightBackground" format="color|reference"/> <attr name="rightText" format="string"/> </declare-styleable> <declare-styleable name="ArcView"> <attr name="text" format="string"/> <attr name="ciclerColor" format="color"/> <attr name="arcColor" format="color"/> </declare-styleable> </resources>
package com.example.yhadmin.viewdemo.view; /* * @项目名: ViewDemo * @包名: com.example.yhadmin.viewdemo.view * @文件名: ArcView * @创建者: YHAdmin * @创建时间: 2017/12/7 16:09 * @描述: TODO */ import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.RectF; import android.support.annotation.Nullable; import android.util.AttributeSet; import android.view.View; import com.example.yhadmin.viewdemo.R; public class ArcView extends View { private int mSpecSizeHeight; private int mSpecSizeWidth; private float mCicleXY; private float mRadius; private Paint mCirclePaint; private RectF mArcRectF; private Paint mArcPaint; private Paint mTextPaint; private float mSweepAngle; private String mShowText; private float mShowTextSize; private float mSweepValue = 66; private int mArcColor; private int mCiclerColor; private String mText; public ArcView(Context context) { this(context, null); } public ArcView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public ArcView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ArcView); mArcColor = ta.getColor(R.styleable.ArcView_arcColor, getResources().getColor(android.R.color.holo_blue_bright)); mCiclerColor = ta.getColor(R.styleable.ArcView_ciclerColor, getResources().getColor(android.R.color.holo_blue_bright)); mText = ta.getString(R.styleable.ArcView_text); ta.recycle(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { mSpecSizeHeight = MeasureSpec.getSize(heightMeasureSpec); mSpecSizeWidth = MeasureSpec.getSize(widthMeasureSpec); setMeasuredDimension(mSpecSizeWidth, mSpecSizeHeight); initView(); } private void initView() { float length = 0; if (mSpecSizeHeight > mSpecSizeWidth) { length = mSpecSizeWidth; } else { length = mSpecSizeHeight; } //圆心和半径 mCicleXY = length / 2; mRadius = (float) (length * 0.5 / 2); mCirclePaint = new Paint(); mCirclePaint.setAntiAlias(true); mCirclePaint.setColor(mCiclerColor); 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.setAntiAlias(true); mArcPaint.setColor(mArcColor); mArcPaint.setStrokeWidth((float) (length * 0.1)); mArcPaint.setStyle(Paint.Style.STROKE); mShowText = setShowText(); mShowTextSize = setShowTextSize(); mTextPaint = new Paint(); mTextPaint.setTextSize(mShowTextSize); mTextPaint.setTextAlign(Paint.Align.CENTER); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawCircle(mCicleXY,mCicleXY,mRadius,mCirclePaint); canvas.drawArc(mArcRectF,270, mSweepAngle, false, mArcPaint); canvas.drawText(mShowText, 0, mShowText.length(), mCicleXY, mCicleXY + (mShowTextSize / 4), mTextPaint); } private float setShowTextSize() { this.invalidate(); return 50; } private String setShowText() { this.invalidate(); return mText; } public void forceInvalidate() { this.invalidate(); } public void setSweepValue(float sweepValue) { if (sweepValue != 0) { mSweepValue = sweepValue; } else { mSweepValue = 25; } this.invalidate(); } }
4.继承ViewGroup 派生特殊的layout,通过组合来实现新的控件
public class HorizontalScrollViewEx extends ViewGroup { private static final String TAG = "HorizontalScrollViewEx"; private int mChildrenSize; private int mChildWidth; private int mChildIndex; // 分别记录上次滑动的坐标 private int mLastX = 0; private int mLastY = 0; // 分别记录上次滑动的坐标(onInterceptTouchEvent) private int mLastXIntercept = 0; private int mLastYIntercept = 0; private Scroller mScroller; private VelocityTracker mVelocityTracker; public HorizontalScrollViewEx(Context context) { super(context); init(); } public HorizontalScrollViewEx(Context context, AttributeSet attrs) { super(context, attrs); init(); } public HorizontalScrollViewEx(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(); } private void init() { if (mScroller == null) { mScroller = new Scroller(getContext()); mVelocityTracker = VelocityTracker.obtain(); } } @Override public boolean onInterceptTouchEvent(MotionEvent event) { boolean intercepted = false; int x = (int) event.getX(); int y = (int) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: { intercepted = false; if (!mScroller.isFinished()) { mScroller.abortAnimation(); intercepted = true; } break; } case MotionEvent.ACTION_MOVE: { int deltaX = x - mLastXIntercept; int deltaY = y - mLastYIntercept; if (Math.abs(deltaX) > Math.abs(deltaY)) { intercepted = true; } else { intercepted = false; } break; } case MotionEvent.ACTION_UP: { intercepted = false; break; } default: break; } Log.d(TAG, "intercepted=" + intercepted); mLastX = x; mLastY = y; mLastXIntercept = x; mLastYIntercept = y; return intercepted; } @Override public boolean onTouchEvent(MotionEvent event) { mVelocityTracker.addMovement(event); int x = (int) event.getX(); int y = (int) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: { if (!mScroller.isFinished()) { mScroller.abortAnimation(); } break; } case MotionEvent.ACTION_MOVE: { int deltaX = x - mLastX; int deltaY = y - mLastY; scrollBy(-deltaX, 0); break; } case MotionEvent.ACTION_UP: { int scrollX = getScrollX(); mVelocityTracker.computeCurrentVelocity(1000); float xVelocity = mVelocityTracker.getXVelocity(); if (Math.abs(xVelocity) >= 50) { mChildIndex = xVelocity > 0 ? mChildIndex - 1 : mChildIndex + 1; } else { mChildIndex = (scrollX + mChildWidth / 2) / mChildWidth; } mChildIndex = Math.max(0, Math.min(mChildIndex, mChildrenSize - 1)); int dx = mChildIndex * mChildWidth - scrollX; smoothScrollBy(dx, 0); mVelocityTracker.clear(); break; } default: break; } mLastX = x; mLastY = y; return true; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int measuredWidth = 0; int measuredHeight = 0; final int childCount = getChildCount(); measureChildren(widthMeasureSpec, heightMeasureSpec); int widthSpaceSize = MeasureSpec.getSize(widthMeasureSpec); int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); int heightSpaceSize = MeasureSpec.getSize(heightMeasureSpec); int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); if (childCount == 0) { setMeasuredDimension(0, 0); } else if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) { final View childView = getChildAt(0); measuredWidth = childView.getMeasuredWidth() * childCount; measuredHeight = childView.getMeasuredHeight(); setMeasuredDimension(measuredWidth, measuredHeight); } else if (heightSpecMode == MeasureSpec.AT_MOST) { final View childView = getChildAt(0); measuredHeight = childView.getMeasuredHeight(); setMeasuredDimension(widthSpaceSize, childView.getMeasuredHeight()); } else if (widthSpecMode == MeasureSpec.AT_MOST) { final View childView = getChildAt(0); measuredWidth = childView.getMeasuredWidth() * childCount; setMeasuredDimension(measuredWidth, heightSpaceSize); } } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int childLeft = 0; final int childCount = getChildCount(); mChildrenSize = childCount; for (int i = 0; i < childCount; i++) { final View childView = getChildAt(i); if (childView.getVisibility() != View.GONE) { final int childWidth = childView.getMeasuredWidth(); mChildWidth = childWidth; childView.layout(childLeft, 0, childLeft + childWidth, childView.getMeasuredHeight()); childLeft += childWidth; } } } private void smoothScrollBy(int dx, int dy) { mScroller.startScroll(getScrollX(), 0, dx, 0, 500); invalidate(); } @Override public void computeScroll() { if (mScroller.computeScrollOffset()) { scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); postInvalidate(); } } @Override protected void onDetachedFromWindow() { mVelocityTracker.recycle(); super.onDetachedFromWindow(); } }