Android 基础-------自定义控件
2016-12-21 23:45
309 查看
关于Android 的自定义控件
在Android 中自定义控件可以分为三种
1.android 原生控件的扩展
2.组合控件(增加新的属性等)
3.通过重写onMearse onLayout onDraw以及OnTouch 完全自定义的全新控件
一 原生扩展空间
该种模式的自定义控件是 由于原生控件无法完全满足需求,从而对对原本控件进行扩展 。
通常自定义一个空间 去继承需要扩展的控件,对onDraw onTouch 进行扩写 或者 重写设计事件分发机制,对事件分发的三个函数 进行修改 (dispatchTouchEvent onInterceptTouchEvent
onTouchEvent)
示例代码:
二 组合控件
该种模式的自定义控件主要是将原有的控件进行整合
该种方式 通过加载xml的方式 将多个原生控件整合在一起,使用自定义属性的方式队员友控件进行设置 通过自定义接口 进行回掉
示例代码:
在使用自定义属性的时候 需要引入命名空间
在这里 mytitle 是随意命名的但是命名结束后在使用自定义属性的时候需要使用这个命名作为前缀 类似于android:
三、 纯自定义控件
这里自定义控件是既不是在原有空间的基础上进行扩充(完全保留院士功能),又不是对多种原生控件进行组合使用 。
而是当现有的控件 无法满足需求的时候完全自定义的控件。
这类空间中一般继承view 或者viewGroup
示例:
1.折线图-----继承View
2.仿ScrollView ---继承ViewGroup
以上为基本的三大类自定义控件的简单使用,在实际使用的时候会根据业务需求进行更多的扩充,以及对事件分发的处理
在Android 中自定义控件可以分为三种
1.android 原生控件的扩展
2.组合控件(增加新的属性等)
3.通过重写onMearse onLayout onDraw以及OnTouch 完全自定义的全新控件
一 原生扩展空间
该种模式的自定义控件是 由于原生控件无法完全满足需求,从而对对原本控件进行扩展 。
通常自定义一个空间 去继承需要扩展的控件,对onDraw onTouch 进行扩写 或者 重写设计事件分发机制,对事件分发的三个函数 进行修改 (dispatchTouchEvent onInterceptTouchEvent
onTouchEvent)
示例代码:
/** * 简单的自定义控件 继承Android 的TextView 控件 进行扩充 * 该控件的ondraw方法保留了TextView 中的部分 */ public class MyTextView extends TextView{ private int mViewWith; private LinearGradient linearGradient; private Matrix matrix; private int translate; public MyTextView(Context context) { super(context); } public MyTextView(Context context, AttributeSet attrs) { super(context, attrs); } public MyTextView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } public MyTextView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); if(mViewWith==0){ mViewWith=getMeasuredWidth(); if(mViewWith>0){ //getPaint 获取绘制text的 画笔 Paint paint = getPaint(); linearGradient =new LinearGradient(0,0,mViewWith,0,new int[]{Color.BLUE,0xffffffff,Color.BLUE},null, Shader.TileMode.CLAMP); paint.setShader(linearGradient); matrix = new Matrix(); } } } @Override protected void onDraw(Canvas canvas) { //在super之前进行操作 是在原本的TextView 绘制文字之前进行其他绘制 //super 正常的绘制文字 super.onDraw(canvas); //super 之后执行 绘制完文字之后进行处理 if(matrix!=null){ translate+=mViewWith/5; if(translate>2*mViewWith){ translate=-mViewWith; } matrix.setTranslate(translate,0); linearGradient.setLocalMatrix(matrix); postInvalidateDelayed(100); } } }
二 组合控件
该种模式的自定义控件主要是将原有的控件进行整合
该种方式 通过加载xml的方式 将多个原生控件整合在一起,使用自定义属性的方式队员友控件进行设置 通过自定义接口 进行回掉
示例代码:
/** * Created by xgd6612 on 2016/12/25. * 简单的组合空间 这里只是简单的进行了控件的组合使用 */ public class MyTitleBar extends RelativeLayout implements View.OnClickListener { private String titleName; private TextViewOnClickListener textViewOnClickListener; public interface TextViewOnClickListener{ void LeftClick(); } public void setTextViewOnClickListener(TextViewOnClickListener listener){ textViewOnClickListener=listener; } public MyTitleBar(Context context) { super(context); } public MyTitleBar(Context context, AttributeSet attrs) { super(context, attrs); View view = View.inflate(context, R.layout.MyTitleBar, this); TextView title = (TextView) view.findViewById(R.id.tv_title); TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MyTitleBar); //在这里 typedArray 通过使用getXXX 的方法进行属性获取,第一个参数是属性名 如果有第二个参数的话 第二个参数返回的是默认值 titleName=typedArray.getString(R.styleable.MyTitleBar_title); title.setText(titleName); title.setOnClickListener(this); typedArray.recycle(); } public MyTitleBar(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override public void onClick(View v) { int id=v.getId(); if(id==R.id.tv_title){ textViewOnClickListener.LeftClick(); } } }
在使用自定义属性的时候 需要引入命名空间
xmlns:mytitle="http://schemas.android.com/apk/res-auto"
在这里 mytitle 是随意命名的但是命名结束后在使用自定义属性的时候需要使用这个命名作为前缀 类似于android:
三、 纯自定义控件
这里自定义控件是既不是在原有空间的基础上进行扩充(完全保留院士功能),又不是对多种原生控件进行组合使用 。
而是当现有的控件 无法满足需求的时候完全自定义的控件。
这类空间中一般继承view 或者viewGroup
示例:
1.折线图-----继承View
public class LineChatView extends View { //定义变量月份 12个月 private final String[] MONTH = new String[]{"","","","","","","","","","","",""}; //定义纵坐标 private String[] yTitlesStrings = {"10", "8", "6", "4", "2"}; //宽度 private int width; //高度 private int height; //左宽 private int leftWidth; //左高 private int leftHeight; private int firstStep; private int columnWidth; //自定义接口 private OnSelctMonthListener onSelctMonthListener; //选择月份 private int selectMonth = -1; // private double ratio; //线程池 private ExecutorService pool; private boolean stopThread; //单位 private String units = "元"; private float startHeight; private int tag; //选中月份 private int selectTextMonth = -1; private int radio = 10; private double[] mData = new double[12]; // private double[] mRadio = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; // 坐标轴水平内部 虚线画笔 private Paint hLinePaint; // 坐标轴 画笔 private Paint xyPaint; // 绘制文本的 画笔 private Paint titlePaint; // 圆环画笔 private Paint recPaint; //折线画笔 private Paint linePaint; //白色画笔 private Paint whitePaint; //选中点画笔 private Paint selectPaint; //y轴坐标 private String[][] ytitile = {{"10", "8", "6", "4", "2"}, {"5", "4", "3", "2", "1"}, {"2.5", "2", "1.5", "1", "0.5"}, {"1000", "800", "600", "400", "200"}, {"500", "400", "300", "200", "100"}, {"250", "200", "150", "100", "50"}, {"100", "80", "60", "40", "20"}, {"50", "40", "30", "20", "10"}, {"25", "20", "15", "10", "5"}, {"10000", "8000", "6000", "4000", "2000"}, {"5000", "4000", "3000", "2000", "1000"}, {"2500", "2000", "1500", "1000", "500"}}; //构造函数 public LineChatView(Context context) { super(context); init(); } //构造函数 public LineChatView(Context context, AttributeSet attrs) { super(context, attrs); init(); } //构造函数 public LineChatView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } //也是一个构造函数 @TargetApi(Build.VERSION_CODES.LOLLIPOP) public LineChatView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); init(); } /** * 根据手机的分辨率从 px(像素) 的单位 转成为 dp */ public static int px2dip(Context context, float dpValue) { final float scale = context.getResources().getDisplayMetrics().density; return (int) (dpValue * scale + 0.5f); } //确定月份的 传入一个月份 public void setSelectMonth(int selectMonth) { this.selectMonth = selectMonth; } //一个监听的方法 传入一个接口实现 public LineChatView setOnSelctMonthListener(OnSelctMonthListener onSelctMonthListener) { this.onSelctMonthListener = onSelctMonthListener; return this; } //初始化 private void init() { hLinePaint = new Paint(); titlePaint = new Paint(); linePaint = new Paint(); recPaint = new Paint(); whitePaint = new Paint(); selectPaint = new Paint(); xyPaint = new Paint(); //设置抗锯齿 whitePaint.setAntiAlias(true); recPaint.setAntiAlias(true); linePaint.setAntiAlias(true); titlePaint.setAntiAlias(true); // hLinePaint.setAntiAlias(true); xyPaint.setAntiAlias(true); //设置画笔颜色 recPaint.setColor(Color.parseColor("#0570e9")); xyPaint.setColor(Color.parseColor("#8f8f8f")); whitePaint.setColor(Color.parseColor("#FFFFFF")); hLinePaint.setColor(Color.parseColor("#ffd8d8d8")); titlePaint.setColor(Color.parseColor("#999999")); linePaint.setColor(Color.parseColor("#0570e9")); linePaint.setStyle(Paint.Style.STROKE); linePaint.setStrokeWidth(3); //指定一个线程的线程池 pool = Executors.newFixedThreadPool(1); } public void setmData(final double[] mData) { stopThread = true; this.mData = mData; final double max = max(mData); units = "元"; if (max > 100000000) { units = "亿元"; if (max > 500000000) { ratio = 100000000; yTitlesStrings = ytitile[0]; } else if (max > 250000000) { ratio = 50000000; yTitlesStrings = ytitile[1]; } else { ratio = 25000000; yTitlesStrings = ytitile[2]; } } else if (max > 10000000) { units = "万元"; if (max > 50000000) { ratio = 10000000; yTitlesStrings = ytitile[9]; } else if (max > 25000000) { ratio = 5000000; yTitlesStrings = ytitile[10]; } else { ratio = 2500000; yTitlesStrings = ytitile[11]; } } else if (max > 1000000) { units = "万元"; if (max > 5000000) { ratio = 1000000; yTitlesStrings = ytitile[3]; } else if (max > 2500000) { ratio = 500000; yTitlesStrings = ytitile[4]; } else { ratio = 250000; yTitlesStrings = ytitile[5]; } } else if (max > 100000) { units = "万元"; if (max > 500000) { ratio = 100000; yTitlesStrings = ytitile[6]; } else if (max > 250000) { ratio = 50000; yTitlesStrings = ytitile[7]; } else { ratio = 25000; yTitlesStrings = ytitile[8]; } } else if (max > 10000) { units = "万元"; if (max > 50000) { ratio = 10000; yTitlesStrings = ytitile[0]; } else if (max > 25000) { ratio = 5000; yTitlesStrings = ytitile[1]; } else { ratio = 2500; yTitlesStrings = ytitile[2]; } } else if (max > 1000) { units = "千元"; if (max > 5000) { ratio = 1000; yTitlesStrings = ytitile[0]; } else if (max > 2500) { ratio = 500; yTitlesStrings = ytitile[1]; } else { ratio = 250; yTitlesStrings = ytitile[2]; } } else if (max > 100) { units = "元"; if (max > 500) { ratio = 100; yTitlesStrings = ytitile[3]; } else if (max > 250) { ratio = 50; yTitlesStrings = ytitile[4]; } else { ratio = 25; yTitlesStrings = ytitile[5]; } } else if (max > 10) { units = "元"; if (max > 50) { ratio = 10; yTitlesStrings = ytitile[6]; } else if (max > 25) { ratio = 5; yTitlesStrings = ytitile[7]; } else { ratio = 2.5; yTitlesStrings = ytitile[8]; } } else { ratio = 1; units = "元"; yTitlesStrings = ytitile[0]; } final double[] sub = new double[mData.length]; final double[] MyNumbles = new double[mData.length]; for (int i = 0; i < MyNumbles.length; i++) { MyNumbles[i] = 1.0 - mData[i] * 1.0 / ratio / 10; sub[i] = (MyNumbles[i] - mRadio[i]) / 300; } SystemClock.sleep(2); stopThread = false; pool.execute(new Thread() { @Override public void run() { for (int i = 0; i < 300; i++) { if (stopThread) { return; } for (int j = 0; j < mRadio.length; j++) { mRadio[j] += sub[j]; if (i == 299 && mData[j] == 0) { mRadio[j] = 1; } } SystemClock.sleep(1); postInvalidate(); //可以子线程 更新视图的方法调用。 } } }); } @Override protected void onDraw(Canvas canvas) { width = getWidth(); height = getHeight(); leftWidth = width / 15; int rightWidth = width / 24; columnWidth = (width - leftWidth - rightWidth) / MONTH.length; int textSize = (int) (columnWidth / 2.5); leftHeight = (int) (height - textSize * 3.5); startHeight = (float) (textSize * 2); titlePaint.setTextAlign(Paint.Align.LEFT); canvas.drawText("单位:" + units, leftWidth, textSize, titlePaint); //画y轴 canvas.drawLine(leftWidth, 0, leftWidth, leftHeight, hLinePaint); //画y坐标 int hPerHeight = (leftHeight - height / 60) / yTitlesStrings.length; titlePaint.setTextSize(textSize); titlePaint.setTextAlign(Paint.Align.CENTER); for (int i = 0; i < yTitlesStrings.length; i++) { canvas.drawText(yTitlesStrings[i], leftWidth / 2, i * hPerHeight + height / 30 + startHeight, titlePaint); canvas.drawLine(leftWidth, i * hPerHeight + height / 60 + startHeight, width - rightWidth, i * hPerHeight + height / 60 + startHeight, hLinePaint); } //画x轴 canvas.drawLine(rightWidth, leftHeight + startHeight, width - rightWidth, leftHeight + startHeight, xyPaint); //画x坐标 titlePaint.setTextSize(textSize); firstStep = columnWidth / 2; for (int i = 0; i < MONTH.length; i++) { canvas.drawText(MONTH[i].substring(0, MONTH[i].length() > 3 ? 3 : MONTH[i].length()), leftWidth + firstStep + columnWidth * i, (float) (height - 0.1 * textSize), titlePaint); } //画线 for (int i = 0; i < MONTH.length; i++) { float circleHeight = 0; double mRadio = this.mRadio[i]; circleHeight = (float) ((leftHeight - height / 60) * mRadio) + height / 60 + startHeight; //画线 if (i < MONTH.length - 1) { float circleHeight2 = 0; mRadio = this.mRadio[i + 1]; circleHeight2 = (float) ((leftHeight - height / 60) * mRadio) + height / 60 + startHeight; canvas.drawLine(leftWidth + firstStep + columnWidth * i, circleHeight, leftWidth + firstStep + columnWidth * (i + 1), circleHeight2, linePaint); } } //画点 for (int i = 0; i < MONTH.length; i++) { float circleHeight = 0; double mRadio = this.mRadio[i]; circleHeight = (float) ((leftHeight - height / 60) * mRadio) + height / 60 + startHeight; //画点 int circleWidth = px2dip(getContext(), 4); if (mRadio != 1) { if (i + 1 == selectMonth) {//高亮 RadialGradient mRadialGradient = new RadialGradient(leftWidth + firstStep + columnWidth * i, circleHeight, px2dip(getContext(), 10), new int[]{ Color.argb(100, 0, 0, 255), Color.argb(0, 0, 0, 255)}, null, Shader.TileMode.REPEAT); selectPaint.setShader(mRadialGradient); canvas.drawCircle(leftWidth + firstStep + columnWidth * i, circleHeight, px2dip(getContext(), 10), selectPaint); circleWidth = px2dip(getContext(), 5); } canvas.drawCircle(leftWidth + firstStep + columnWidth * i, circleHeight, circleWidth, recPaint); canvas.drawCircle(leftWidth + firstStep + columnWidth * i, circleHeight, px2dip(getContext(), 3), whitePaint); } } } //自定义 点击触摸事件 @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: for (int i = 0; i < MONTH.length; i++) { float circleHeight = 0; double mRadio = this.mRadio[i]; circleHeight = (float) ((leftHeight - height / 60) * mRadio) + height / 60 + startHeight; //画点 // canvas.drawCircle(leftWidth+firstStep+columnWidth*i,circleHeight,10,recPaint); int circleX = leftWidth + firstStep + columnWidth * i; float textHeight = (float) (height - 0.1 * (columnWidth / 2.5)); //(float) (height-0.1*textSize) y轴坐标 if (event.getX() > circleX - 30 && event.getX() < circleX + 30 && event.getY() > circleHeight - 30 && event.getY() < circleHeight + 30) { if (this.mData[i] != 0) { selectMonth = i + 1; postInvalidate(); } if (onSelctMonthListener != null) { tag = 1; } } else if (event.getX() > circleX - 30 && event.getX() < circleX + 30 && event.getY() > textHeight - 40 && event.getY() < textHeight + 40) { if (this.mData[i] != 0) { selectMonth = i + 1; postInvalidate(); } tag = 2; } } break; case MotionEvent.ACTION_UP: if (tag == 1) { for (int i = 0; i < MONTH.length; i++) { if (this.mData[i] != 0) { float circleHeight = 0; double mRadio = this.mRadio[i]; circleHeight = (float) ((leftHeight - height / 60) * mRadio) + height / 60 + startHeight; int circleX = leftWidth + firstStep + columnWidth * i; if (event.getX() > circleX - 30 && event.getX() < circleX + 30 && event.getY() > circleHeight - 50 && event.getY() < circleHeight + 50) { selectMonth = i + 1; postInvalidate(); onSelctMonthListener.selectMonth(selectMonth); tag = 0; } } } } if (tag == 2) { for (int i = 0; i < MONTH.length; i++) { if (this.mData[i] != 0) { double mRadio = this.mRadio[i]; float textHeight = (float) (height - 0.1 * (columnWidth / 2.5)); int circleX = leftWidth + firstStep + columnWidth * i; if (event.getX() > circleX - 30 && event.getX() < circleX + 30 && event.getY() > textHeight - 40 && event.getY() < textHeight + 40) { selectMonth = i + 1; postInvalidate(); onSelctMonthListener.selectMonth(selectMonth); tag = 0; } } } break; } } return true; } private double max(double[] arr) { double max = 0; if (arr != null) { max = arr[0]; for (Double i : arr) { if (i > max) { max = i; } } } return max; } //定义接口 public interface OnSelctMonthListener { void selectMonth(int i); } //定义接口 public interface onSelctTextMonthListener { void selectMonth(int i); } }
2.仿ScrollView ---继承ViewGroup
public class MyGroupView extends ViewGroup { private int mScreenHeight; private int lastY; private int start; private int endY; private int dScrollY; private Scroller mScroller; public MyGroupView(Context context) { super(context); init(context); } public MyGroupView(Context context, AttributeSet attrs) { super(context, attrs); init(context); } public MyGroupView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context); } public MyGroupView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); init(context); } private void init(Context context) { mScreenHeight=getResources().getDisplayMetrics().heightPixels; mScroller=new Scroller(context); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int count=getChildCount(); //使用遍历的方式进行子view的测量 int i=0; for(;i<count;i++){ View childView =getChildAt(i); measureChild(childView,widthMeasureSpec,heightMeasureSpec); } } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int childCount =getChildCount(); MarginLayoutParams marginLayoutParams = (MarginLayoutParams) getLayoutParams(); marginLayoutParams.height=mScreenHeight+childCount; setLayoutParams(marginLayoutParams); int i=0; for(;i<childCount;i++){ View child =getChildAt(i); if(child.getVisibility()!= View.GONE){ child.layout(l,i*mScreenHeight,r,(i+1)*mScreenHeight); } } } @Override public boolean onTouchEvent(MotionEvent event) { int y= (int) event.getY(); switch (event.getAction()){ case MotionEvent.ACTION_DOWN: lastY=y; start=getScrollY(); break; case MotionEvent.ACTION_MOVE: if(mScroller.isFinished()){ mScroller.abortAnimation(); } int dy = lastY-y; if(getScrollY()<0){ dy=0; } if(getScrollY()>getHeight()-mScreenHeight){ dy=0; } scrollBy(dy,0); lastY=y; break; case MotionEvent.ACTION_UP: endY=getScrollY(); dScrollY=endY-start; if(dScrollY>0){ if(dScrollY<mScreenHeight/3){ mScroller.startScroll(0, getScrollY(),0,-dScrollY); }else{ mScroller.startScroll(0, getScrollY(),0,mScreenHeight-dScrollY); } }else{ if(-dScrollY<mScreenHeight/3){ mScroller.startScroll(0, getScrollY(),0,-dScrollY); }else{ mScroller.startScroll(0, getScrollY(),0,-mScreenHeight-dScrollY); } } break; } postInvalidate(); return true; } @Override public void computeScroll() { super.computeScroll(); if(mScroller.computeScrollOffset()){ scrollTo(0,mScroller.getCurrY()); postInvalidate(); } } }
以上为基本的三大类自定义控件的简单使用,在实际使用的时候会根据业务需求进行更多的扩充,以及对事件分发的处理
相关文章推荐
- android基础笔记——自定义控件和视图:放ios系统开关按钮
- Android自定义控件前导基础知识学习(一)——Canvas
- Android自定义控件基础
- Android 自定义控件基础 canvas paint
- Android自定义控件_基础(五)
- android基础笔记——自定义控件和视图:优酷菜单:RotateAnimation
- Android-自定义控件基础-图片预览和多点触控
- Android 自定义控件之基础几何图形绘制详解
- Android自定义控件:绘图基础Canvas、Paint类
- Android-自定义控件基础-抽奖转盘(SurfaceView)
- Android自定义控件前导基础知识学习(一)——Canvas
- android开发基础之---------------自定义控件的使用
- Android自定义控件开发系列(零)——基础原理篇
- android 自定义控件基础知识1
- Android 自定义控件学习之一 基础知识
- android基础之自定义控件-topbar
- android 自定义控件基础之三种约束类型
- Android基础之自定义控件、布局以及ListView控件
- [自定义控件]android自定义view基础
- Android-自定义控件基础-流式布局