之前在我的另外一个帐号中,发了一篇博文
博客地址:http://blog.csdn.net/u012790647/article/details/16007559
不涉及版权的情况下,某些源码不是你的也不是我的,源码是共享的。交流才能进步。。。。。不重复造*创新才是王道
先上图:这是本博文的自定义控件
使用的就是这篇博文将要写的一个自定义view,MyButton这个控件可以参考sdk中:
LabelView
/* * Copyright (C) 2007 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.example.android.apis.view; // Need the following import to get access to the app resources, since this // class is in a sub-package. import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Paint; import android.util.AttributeSet; import android.view.View; import com.example.android.apis.R; /** * Example of how to write a custom subclass of View. LabelView * is used to draw simple text views. Note that it does not handle * styled text or right-to-left writing systems. * */ public class LabelView extends View { private Paint mTextPaint; private String mText; private int mAscent; /** * Constructor. This version is only needed if you will be instantiating * the object manually (not from a layout XML file). * @param context */ public LabelView(Context context) { super(context); initLabelView(); } /** * Construct object, initializing with any attributes we understand from a * layout file. These attributes are defined in * SDK/assets/res/any/classes.xml. * * @see android.view.View#View(android.content.Context, android.util.AttributeSet) */ public LabelView(Context context, AttributeSet attrs) { super(context, attrs); initLabelView(); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.LabelView); CharSequence s = a.getString(R.styleable.LabelView_text); if (s != null) { setText(s.toString()); } // Retrieve the color(s) to be used for this view and apply them. // Note, if you only care about supporting a single color, that you // can instead call a.getColor() and pass that to setTextColor(). setTextColor(a.getColor(R.styleable.LabelView_textColor, 0xFF000000)); int textSize = a.getDimensionPixelOffset(R.styleable.LabelView_textSize, 0); if (textSize > 0) { setTextSize(textSize); } a.recycle(); } private final void initLabelView() { mTextPaint = new Paint(); mTextPaint.setAntiAlias(true); // Must manually scale the desired text size to match screen density mTextPaint.setTextSize(16 * getResources().getDisplayMetrics().density); mTextPaint.setColor(0xFF000000); setPadding(3, 3, 3, 3); } /** * Sets the text to display in this label * @param text The text to display. This will be drawn as one line. */ public void setText(String text) { mText = text; requestLayout(); invalidate(); } /** * Sets the text size for this label * @param size Font size */ public void setTextSize(int size) { // This text size has been pre-scaled by the getDimensionPixelOffset method mTextPaint.setTextSize(size); requestLayout(); invalidate(); } /** * Sets the text color for this label. * @param color ARGB value for the text */ public void setTextColor(int color) { mTextPaint.setColor(color); invalidate(); } /** * @see android.view.View#measure(int, int) */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec)); } /** * Determines the width of this view * @param measureSpec A measureSpec packed into an int * @return The width of the view, honoring constraints from measureSpec */ private int measureWidth(int measureSpec) { int result = 0; int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); if (specMode == MeasureSpec.EXACTLY) { // We were told how big to be result = specSize; } else { // Measure the text result = (int) mTextPaint.measureText(mText) + getPaddingLeft() + getPaddingRight(); if (specMode == MeasureSpec.AT_MOST) { // Respect AT_MOST value if that was what is called for by measureSpec result = Math.min(result, specSize); } } return result; } /** * Determines the height of this view * @param measureSpec A measureSpec packed into an int * @return The height of the view, honoring constraints from measureSpec */ private int measureHeight(int measureSpec) { int result = 0; int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); mAscent = (int) mTextPaint.ascent(); if (specMode == MeasureSpec.EXACTLY) { // We were told how big to be result = specSize; } else { // Measure the text (beware: ascent is a negative number) result = (int) (-mAscent + mTextPaint.descent()) + getPaddingTop() + getPaddingBottom(); if (specMode == MeasureSpec.AT_MOST) { // Respect AT_MOST value if that was what is called for by measureSpec result = Math.min(result, specSize); } } return result; } /** * Render the text * * @see android.view.View#onDraw(android.graphics.Canvas) */ @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawText(mText, getPaddingLeft(), getPaddingTop() - mAscent, mTextPaint); } }
然后在其基础上扩展,今天写的这个,关于写的一个自定义控件。
在进入商品详细画面后,会呈现出这个商品的相关颜色,尺码属性。最近公司闲的蛋疼,叫加上这个功能,加了也一直放着不知道肿么?,于是这几天就弄了下,虽说是个自定义控件但感觉高级不了哪里去,但也是辛勤劳动成果。贴上来,或许某些情况下还用的着。并不是100%原创,在customLinearlayout也参考过别人的代码(http://blog.csdn.net/long704480904/article/details/9011115)、所谓不重复造*嘛,就看你怎么发挥了。参考也说的理直气壮,嘿嘿,自我嘲讽一下。
先说下图片中的Button(extends View )
package com.example.androidcustomwidget; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Paint.Align; import android.graphics.Paint.FontMetrics; import android.graphics.Paint.Style; import android.graphics.Path; import android.graphics.Rect; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; /** * * 自定义控件 */ public class MyButton extends View { /** * 画笔,包含了画几何图形、文本等的样式和颜色信息 */ private Paint mPaint; /** * 控件文本 */ private String _text = "二零一三年"; /** * 字体颜色 */ private int textColor = Color.BLACK; /** * 字体大小 */ private float textSize = 30;// 在MEIZU MX2上必须要设置这个值才行 一般手机上只要20就够大了。。。。。 /** * 存储当前按下状态 */ private boolean _isPress = false; /** * 存储当前选中状态 */ private boolean _isChecked = false; /** * 控件的id 该自定义控件的事件处理已经在onTouchEvent里面处理,再调用回调,可以不要id属性 */ private int m_id; /** * 选中状态改变后,触发回调 */ private ChangedCheckCallBackAttri mCallback; private int _mAscent; private int mWidth = 0; private int mHeight = 0; public int getmWidth() { return mWidth; } public void setmWidth(int mWidth) { this.mWidth = mWidth; } public int getmHeight() { return mHeight; } public void setmHeight(int mHeight) { this.mHeight = mHeight; } public MyButton(Context context, String name) { super(context); mPaint = new Paint(); setPadding(10, 5, 10, 5); mPaint.setColor(textColor); mPaint.setTextSize(textSize); mPaint.setAntiAlias(true); mPaint.setDither(true); this.setText(name); // mPaint.setTypeface(Typeface.DEFAULT_BOLD) ;//设置字体 } public MyButton(Context context, AttributeSet attrs) { super(context, attrs); mPaint = new Paint(); mPaint.setAntiAlias(true);// 抗锯齿 mPaint.setDither(true);// 图像抖动处理 setPadding(10, 5, 10, 5); // TypedArray是一个用来存放由context.obtainStyledAttributes获得的属性的数组 // 在使用完成后,一定要调用recycle方法 // 属性的名称是styleable中的名称+“_”+属性名称 TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.MyButton); // String strText = array.getText(R.styleable.jwradiobtn_text, ""); CharSequence text = array.getText(R.styleable.MyButton_android_text); if (text != null) _text = text.toString(); textColor = array.getColor(R.styleable.MyButton_textColor, Color.BLACK); // 提供默认值,放置未指定 textSize = array.getDimension(R.styleable.MyButton_textSize, 20); mPaint.setColor(textColor); mPaint.setTextSize(textSize); array.recycle(); // 一定要调用,否则这次的设定会对下次的使用造成影响 } public void onDraw(Canvas canvas) { super.onDraw(canvas); if (_text != null && !_text.equals("")) { mPaint.setTextSize(textSize); mPaint.setColor(textColor); mPaint.setTextAlign(Align.CENTER);// 文字居中显示 mPaint.setStrokeWidth(0); FontMetrics fontMetrics = mPaint.getFontMetrics(); float fontHeight = fontMetrics.bottom - fontMetrics.top;// 文本高度 float baseY = this.getHeight() - (this.getHeight() - fontHeight) / 2 - fontMetrics.bottom; canvas.drawText(_text, this.getWidth() / 2, baseY, mPaint);// (this.getHeight() // - // fontHeight)/ // 2 // __额外 // 设置了mPaint.setTextAlign(Align.CENTER);//文字居中显示 // 所以canvas.drawText(text,x,y,paint);中,x,为横坐标中点位置 } if (_isPress) { // 按下时边框绘制橘黄色 mPaint.setColor(Color.rgb(255, 165, 0)); mPaint.setStyle(Style.STROKE); // 设置填充 mPaint.setStrokeWidth(5); canvas.drawRect(1, 1, this.getWidth() - 1, this.getHeight() - 1, mPaint); // 绘制矩形 return; } if (_isChecked) { // 选中时边框绘制红色 // Canvas中含有很多画图的接口,利用这些接口,我们可以画出我们想要的图形 // mPaint = new Paint(); mPaint.setColor(Color.RED); mPaint.setStyle(Style.STROKE); // 设置填充 mPaint.setStrokeWidth(5); canvas.drawRect(1, 1, this.getWidth() - 1, this.getHeight() - 1, mPaint); // 绘制矩形 // 绘制右下角的三角形 mPaint.setStyle(Style.FILL); Path path = new Path(); path.moveTo(this.getWidth(), this.getHeight()); path.lineTo(this.getWidth(), this.getHeight() - 15); path.lineTo(this.getWidth() - 15, this.getHeight()); canvas.drawPath(path, mPaint); } else { // 选中时边框绘制黑色 mPaint.setColor(Color.BLACK); mPaint.setStyle(Style.STROKE); // 设置填充 mPaint.setStrokeWidth(1); canvas.drawRect(0, 0, this.getWidth(), this.getHeight(), mPaint); // 绘制矩形 } } @Override public boolean dispatchTouchEvent(MotionEvent event) { // TODO Auto-generated method stub // if (event.getAction() == MotionEvent.ACTION_DOWN) // return true; return super.dispatchTouchEvent(event); } @Override public boolean onTouchEvent(MotionEvent event) { // TODO Auto-generated method stub Log.e("JWRadioBtn", "JWRadioBtn_onTouchEvent:" + event.getAction()); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: _isPress = true; this.invalidate();// 重绘,执行onDraw return true;// 要返回true,后面的action_up和move才能执行 case MotionEvent.ACTION_UP: if (mCallback != null) { mCallback.ChangedCheck(this); } _isPress = false; setChecked(!_isChecked); break; case MotionEvent.ACTION_MOVE: break; default: _isPress = false; this.invalidate();// 重绘,执行onDraw break; } return super.onTouchEvent(event); } /** * @see android.view.View#measure(int, int) */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // setMeasuredDimension(measureWidth(widthMeasureSpec), // measureHeight(heightMeasureSpec)); setMeasuredDimension(resolveSize(mWidth, widthMeasureSpec), resolveSize(mHeight, widthMeasureSpec)); } /** * Determines the width of this view * * @param measureSpec * A measureSpec packed into an int * @return The width of the view, honoring constraints from measureSpec */ private int measureWidth(int measureSpec) { int result = 0; int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); if (specMode == MeasureSpec.EXACTLY) { // We were told how big to be result = specSize; } else { // Measure the text result = (int) mPaint.measureText(_text) + getPaddingLeft() + getPaddingRight(); if (specMode == MeasureSpec.AT_MOST) { // Respect AT_MOST value if that was what is called for by // measureSpec result = Math.min(result, specSize); } } return result; } /** * Determines the height of this view * * @param measureSpec * A measureSpec packed into an int * @return The height of the view, honoring constraints from measureSpec */ private int measureHeight(int measureSpec) { int result = 0; int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); _mAscent = (int) mPaint.ascent(); if (specMode == MeasureSpec.EXACTLY) { // We were told how big to be result = specSize; } else { // Measure the text (beware: ascent is a negative number) result = (int) (-_mAscent + mPaint.descent()) + getPaddingTop() + getPaddingBottom(); if (specMode == MeasureSpec.AT_MOST) { // Respect AT_MOST value if that was what is called for by // measureSpec result = Math.min(result, specSize); } } return result; } public void setChecked(boolean value) { _isChecked = value; this.invalidate(); // if (mCallback != null) // mCallback.ChangedCheck(); } /** * * @return 控件状态 true:选中 false :未被选中 */ public boolean getChecked() { return _isChecked; } public void setText(String value) { _text = value; this.invalidate(); } public String getText() { return _text; } public void setTextColor(int value) { textColor = value; this.invalidate(); } public int getTextColor() { return textColor; } public void setTextSize(float value) { textSize = value; this.invalidate(); } public float getTextSize() { return textSize; } public void setId(int id) { this.m_id = id; } public int getId() { return this.m_id; } /** * 获取控件宽度 * * @return 文本总宽度 + padding消耗值 */ public int getRealWidth() { if (this.getWidth() == 0) { Rect rect = new Rect(); mPaint.getTextBounds(_text, 0, _text.length(), rect); return rect.width() + this.getPaddingLeft() + this.getPaddingRight(); } else { return this.getWidth(); } } /** * 获取控件高度 * * @return 文本高度+ pading消耗值 */ public int getRealHeight() { if (this.getHeight() == 0) { Rect rect = new Rect(); mPaint.getTextBounds(_text, 0, _text.length(), rect); return rect.height() + this.getPaddingTop() + this.getPaddingBottom(); } else { return this.getHeight(); } } /** * 接口用于回调 */ public interface ChangedCheckCallBackAttri { void ChangedCheck(MyButton attributeBtn); } public void setCallback(ChangedCheckCallBackAttri callBack) { this.mCallback = callBack; } }
主要的几个方法:public void onDraw(Canvas canvas)、public boolean onTouchEvent(MotionEvent event)、protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
/** * 接口用于回调 */ public interface ChangedCheckCallBackAttri { void ChangedCheck(MyButton attributeBtn); } public void setCallback(ChangedCheckCallBackAttri callBack) { this.mCallback = callBack; }
回调接口主要用于在view被选中的时候,处理一些相关操作,比如我在AttributeMyWidget中的操作:
attriBtn.setCallback(new ChangedCheckCallBackAttri() { @Override public void ChangedCheck(MyButton attributeBtn) { if (attributeBtn.getTag() == null) { return; } int index = Integer.valueOf(attributeBtn.getTag() .toString()); if (index != selectedIndex) { MyButton btn = radioBtnList.get(selectedIndex); btn.setChecked(false); selectedIndex = index; } Toast.makeText(context, "你点击了" + attriDetailList.get(index).get("name"), Toast.LENGTH_SHORT).show(); // String attId = getAttriDetail(); // if (attId.equals("")) { // return; // } // // // 重选属性时,回调,更新库存,价格数据显示 // AttributeMyWidget.this.modifyedListener.modifyed(attId, // flag); } });
因为是动态添加View所以在添加的同时给每个View给了tag,在回调时候就派上用场了,这里我根据tag获得他的index进而做更多操作。
还有个问题:
/** * @see android.view.View#measure(int, int) */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // setMeasuredDimension(measureWidth(widthMeasureSpec), // measureHeight(heightMeasureSpec)); setMeasuredDimension(resolveSize(mWidth, widthMeasureSpec), resolveSize(mHeight, widthMeasureSpec)); }这里mWidth,mHeight是我固定设定的固定宽高。在创建
public AttributeMyWidget(Context context, int childViewWidth, int childViewHeight) {
的时候设定。也可以根据内容来决定其宽高。
说到这里:几者之间关系:MyButton + CustomLinearLayout------>AttributeMyWidget ------>in Activity中new
接下来是CustomLinearLayout
package com.example.androidcustomwidget; import android.content.Context; import android.util.AttributeSet; import android.util.Log; import android.view.View; import android.widget.LinearLayout; /** * * 实现自动换行 * */ public class CustomLinearLayout extends LinearLayout { private Context mContext; public CustomLinearLayout(Context context) { super(context); mContext = context; } public CustomLinearLayout(Context context, AttributeSet attrs) { super(context, attrs); mContext = context; } /** 控件总行数 **/ private int num = 1; /** 按钮总行数 **/ private int columns = 0; /** 设置子view的宽度外部设定,需要根据子view的宽度布局换行时会用 **/ private int mCellWidth = 0; /** 设置子view的高度 **/ private int mCellHeight = 0; /** //layout总宽度 **/ int layoutTotalWidth = 0; public int getLayoutTotalWidth() { return layoutTotalWidth; } public void setLayoutTotalWidth(int layoutTotalWidth) { this.layoutTotalWidth = layoutTotalWidth; } public int getmCellWidth() { return mCellWidth; } public void setmCellWidth(int mCellWidth) { this.mCellWidth = mCellWidth; } public int getmCellHeight() { return mCellHeight; } public void setmCellHeight(int mCellHeight) { this.mCellHeight = mCellHeight; } /** * 根据LinearLayout总宽度自动匹配布局 */ public void autoLayout() { int cellWidth = 0;// 子空间宽 int cellHeight = 0;// 子空间高 int totalWidth = 0;// 每一行总宽度 int x = 0; int y = 0; int count = getChildCount();// 5 for (int j = 0; j < count; j++) { View childView = getChildAt(j);// [35,70] // 获取子控件Child的宽高[38,31] cellWidth = ((MyButton) childView).getRealWidth(); cellHeight = ((MyButton) childView).getRealHeight(); // +5 保持+5间距 int left = x + 5; int top = y; // childView.layout(x, y, x + cellWidth, y + cellHeight); // 布局子控件0 childView.layout(left, top, left + cellWidth, top + cellHeight); x = left + 5; if (totalWidth > this.getLayoutTotalWidth()) { // 换行 x = 0; totalWidth = 0; y += cellHeight + 5; num++; } else { x += cellWidth; totalWidth += x; } } } /** * 根据子view宽度布局 */ public void byChildWidthLayout(boolean changed, int l, int t, int r, int b) { int cellWidth = mCellWidth; int cellHeight = mCellHeight; columns = (r - l) / cellWidth; if (columns < 0) { columns = 1; } int x = 0; int y = 0; int i = 0; int count = getChildCount(); for (int j = 0; j < count; j++) { View childView = getChildAt(j); // 获取子控件Child的宽高 int w = ((MyButton) childView).getmWidth(); int h = ((MyButton) childView).getmHeight(); // 计算子控件的顶点坐标 int left = x + 5; int top = y; // 布局子控件0 childView.layout(left, top, left + w, top + h); x = left + 5; if (i >= (columns - 1)) { i = 0; x = 0; y += cellHeight + 5; num++; } else { i++; x += cellWidth; } } } /** * 自动换行模式下 * * @param widthMeasureSpec * @param heightMeasureSpec */ public void onMeasureByChildContent(int widthMeasureSpec, int heightMeasureSpec) { int count = getChildCount(); int layoutWidth = 0; int childHeight = 0; // 子view的高 int a = this.getLayoutParams().width; int b = this.getLayoutParams().height; // Log.e("customlayout 随内容...", "getLayoutParams宽度"+a // +"**********************" + "getLayoutParams高度"+ b ) ; // 设置子空间Child的宽高 for (int i = 0; i < count; i++) { View childView = getChildAt(i); layoutWidth += ((MyButton) childView).getRealWidth(); childHeight = ((MyButton) childView).getHeight(); } // 设置容器控件所占区域大小 setMeasuredDimension(resolveSize(layoutWidth, widthMeasureSpec), resolveSize(num * childHeight, heightMeasureSpec)); // layoutTotalWidth = this.getWidth() ; Log.e("总宽度", "************************" + layoutTotalWidth); } /** * * 子view宽度来 * * @param widthMeasureSpec * @param heightMeasureSpec */ public void onMeasureByChildWidth(int widthMeasureSpec, int heightMeasureSpec) { // 创建测量参数 int cellWidthSpec = MeasureSpec.makeMeasureSpec(mCellWidth, MeasureSpec.EXACTLY); int cellHeightSpec = MeasureSpec.makeMeasureSpec(mCellHeight, MeasureSpec.EXACTLY); int count = getChildCount(); // 设置子空间Child的宽高 for (int i = 0; i < count; i++) { View childView = getChildAt(i); childView.measure(cellWidthSpec, cellHeightSpec); } // 设置容器控件所占区域大小 setMeasuredDimension( resolveSize(mCellWidth * columns + 5, widthMeasureSpec), resolveSize(mCellHeight * num + 10, heightMeasureSpec)); } /** * 控制子控件的换行 */ @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { byChildWidthLayout(changed, l, t, r, b); // autoLayout(); } /** * 计算控件及子控件所占区域 */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // onMeasureByChildContent(widthMeasureSpec, heightMeasureSpec); onMeasureByChildWidth(widthMeasureSpec, heightMeasureSpec); } }
AttributeMyWidget的布局:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="#E8E8E8" android:orientation="vertical" > <TextView android:id="@+id/attriname" android:layout_width="match_parent" android:layout_height="30dp" android:ellipsize="end" android:gravity="left|center_vertical" android:paddingLeft="5dp" android:singleLine="true" android:text="" android:textColor="#CD2626" android:textSize="20sp" android:textStyle="bold" /> <View android:layout_width="match_parent" android:layout_height="1dp" android:background="#B0B0B0" /> <com.example.androidcustomwidget.CustomLinearLayout android:id="@+id/attri_radiogrouplayout" android:layout_width="800dp" android:layout_height="wrap_content" > </com.example.androidcustomwidget.CustomLinearLayout> </LinearLayout>
最后是MainActivity
package com.example.androidcustomwidget; import android.os.Bundle; import android.app.Activity; import android.view.Menu; import android.widget.LinearLayout; public class MainActivity extends Activity { AttributeMyWidget attributeMyWidget; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); LinearLayout layout = (LinearLayout) findViewById(R.id.layout); // 魅族Mx2手机分辨率比较大因此,这里我设置了[120,80]的大小,通常情况下的话[70,35就差不多了] attributeMyWidget = new AttributeMyWidget(this, 120, 80); // attributeMyWidget = new AttributeMyWidget(this,800) ; attributeMyWidget.loadAttriData(); attributeMyWidget.setName("颜色属性"); layout.addView(attributeMyWidget); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } }
源码下载:http://download.csdn.net/detail/xxm282828/6856641