4、快速实现自定义View
2015-12-17 22:04
417 查看
通常来说,自定义View有三种表现形式:自绘View,组合控件,继承控件。
http://schemas.android.com/apk/res/:自定义属性的前缀。
com.example.circlepregress:斜体部分为包名。
一、自绘View
自绘View,顾名思义,就是为了实现一个效果,但是Google没有提供现成的控件,这个时候,我们可以通过自绘View绘制出想要的View。
1、定义控件属性
我们需要在Values文件夹下新建attrs.xml文件,用来定义控件的属性。
<?xml version="1.0" encoding="UTF-8"?> <resources> <declare-styleable name="RoundProgressBar"> <attr name="roundColor" format="color"/> <attr name="roundProgressColor" format="color"/> <attr name="roundWidth" format="dimension"></attr> <attr name="textColor" format="color" /> <attr name="textSize" format="dimension" /> <attr name="max" format="integer"></attr> <attr name="textIsDisplayable" format="boolean"></attr> <attr name="style"> <enum name="STROKE" value="0"></enum> <enum name="FILL" value="1"></enum> </attr> </declare-styleable> </resources>
其中format有string,color,demension,integer,enum,reference,float,boolean,fraction,flag等类型。
2、在自绘View的构造方法中,获取自定义属性。
我们先调用context.obtainStyledAttributes(attrs,R.styleable.RoundProgressBar)来获取TypedArray,然后从TypedArray获取我们定义的属性。
roundColor = mTypedArray.getColor(R.styleable.RoundProgressBar_roundColor, Color.RED); roundProgressColor = mTypedArray.getColor(R.styleable.RoundProgressBar_roundProgressColor, Color.GREEN); textColor = mTypedArray.getColor(R.styleable.RoundProgressBar_textColor, Color.GREEN); textSize = mTypedArray.getDimension(R.styleable.RoundProgressBar_textSize, 15); roundWidth = mTypedArray.getDimension(R.styleable.RoundProgressBar_roundWidth, 5); max = mTypedArray.getInteger(R.styleable.RoundProgressBar_max, 100); textIsDisplayable = mTypedArray.getBoolean(R.styleable.RoundProgressBar_textIsDisplayable, true); style = mTypedArray.getInt(R.styleable.RoundProgressBar_style, 0);
3、重写onDraw(),onMeasure()方法,其中onMeasure()不是必须要重写。
package com.example.roundprogressbar; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.RectF; import android.graphics.Typeface; import android.util.AttributeSet; import android.util.Log; import android.view.View; import com.example.circlepregress.R; /** * 仿iphone带进度的进度条,线程安全的View,可直接在线程中更新进度 * */ public class RoundProgressBar extends View { /** * 画笔对象 4000 的引用 */ private Paint paint; /** * 圆环的颜色 */ private int roundColor; /** * 圆环进度的颜色 */ private int roundProgressColor; /** * 中间进度百分比的字符串的颜色 */ private int textColor; /** * 中间进度百分比的字符串的字体 */ private float textSize; /** * 圆环的宽度 */ private float roundWidth; /** * 最大进度 */ private int max; /** * 当前进度 */ private int progress; /** * 是否显示中间的进度 */ private boolean textIsDisplayable; /** * 进度的风格,实心或者空心 */ private int style; public static final int STROKE = 0; public static final int FILL = 1; public RoundProgressBar(Context context) { this(context, null); } public RoundProgressBar(Context context, AttributeSet attrs) { this(context, attrs, 0); } public RoundProgressBar(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); paint = new Paint(); TypedArray mTypedArray = context.obtainStyledAttributes(attrs, R.styleable.RoundProgressBar); // 获取自定义属性和默认值 roundColor = mTypedArray.getColor( R.styleable.RoundProgressBar_roundColor, Color.RED); roundProgressColor = mTypedArray.getColor( R.styleable.RoundProgressBar_roundProgressColor, Color.GREEN); textColor = mTypedArray.getColor( R.styleable.RoundProgressBar_textColor, Color.GREEN); textSize = mTypedArray.getDimension( R.styleable.RoundProgressBar_textSize, 15); roundWidth = mTypedArray.getDimension( R.styleable.RoundProgressBar_roundWidth, 5); max = mTypedArray.getInteger(R.styleable.RoundProgressBar_max, 100); textIsDisplayable = mTypedArray.getBoolean( R.styleable.RoundProgressBar_textIsDisplayable, true); style = mTypedArray.getInt(R.styleable.RoundProgressBar_style, 0); mTypedArray.recycle(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); /** * 画最外层的大圆环 */ int centre = getWidth() / 2; // 获取圆心的x坐标 int radius = (int) (centre - roundWidth / 2); // 圆环的半径 paint.setColor(roundColor); // 设置圆环的颜色 paint.setStyle(Paint.Style.STROKE); // 设置空心 paint.setStrokeWidth(roundWidth); // 设置圆环的宽度 paint.setAntiAlias(true); // 消除锯齿 canvas.drawCircle(centre, centre, radius, paint); // 画出圆环 Log.e("log", centre + ""); /** * 画进度百分比 */ paint.setStrokeWidth(0); paint.setColor(textColor); paint.setTextSize(textSize); paint.setTypeface(Typeface.DEFAULT_BOLD); // 设置字体 int percent = (int) (((float) progress / (float) max) * 100); // 中间的进度百分比,先转换成float在进行除法运算,不然都为0 float textWidth = paint.measureText(percent + "%"); // 测量字体宽度,我们需要根据字体的宽度设置在圆环中间 if (textIsDisplayable && percent != 0 && style == STROKE) { canvas.drawText(percent + "%", centre - textWidth / 2, centre + textSize / 2, paint); // 画出进度百分比 } /** * 画圆弧 ,画圆环的进度 */ // 设置进度是实心还是空心 paint.setStrokeWidth(roundWidth); // 设置圆环的宽度 paint.setColor(roundProgressColor); // 设置进度的颜色 RectF oval = new RectF(centre - radius, centre - radius, centre + radius, centre + radius); // 用于定义的圆弧的形状和大小的界限 switch (style) { case STROKE: { paint.setStyle(Paint.Style.STROKE); canvas.drawArc(oval, 0, 360 * progress / max, false, paint); // 根据进度画圆弧 break; } case FILL: { paint.setStyle(Paint.Style.FILL_AND_STROKE); if (progress != 0) canvas.drawArc(oval, 0, 360 * progress / max, true, paint); // 根据进度画圆弧 break; } } } public synchronized int getMax() { return max; } /** * 设置进度的最大值 * * @param max */ public synchronized void setMax(int max) { if (max < 0) { throw new IllegalArgumentException("max not less than 0"); } this.max = max; } /** * 获取进度.需要同步 * * @return */ public synchronized int getProgress() { return progress; } /** * 设置进度,此为线程安全控件,由于考虑多线的问题,需要同步 刷新界面调用postInvalidate()能在非UI线程刷新 * * @param progress */ public synchronized void setProgress(int progress) { if (progress < 0) { throw new IllegalArgumentException("progress not less than 0"); } if (progress > max) { progress = max; } if (progress <= max) { this.progress = progress; postInvalidate(); } } public int getCricleColor() { return roundColor; } public void setCricleColor(int cricleColor) { this.roundColor = cricleColor; } public int getCricleProgressColor() { return roundProgressColor; } public void setCricleProgressColor(int cricleProgressColor) { this.roundProgressColor = cricleProgressColor; } public int getTextColor() { return textColor; } public void setTextColor(int textColor) { this.textColor = textColor; } public float getTextSize() { return textSize; } public void setTextSize(float textSize) { this.textSize = textSize; } public float getRoundWidth() { return roundWidth; } public void setRoundWidth(float roundWidth) { this.roundWidth = roundWidth; } }
4、布局文件中使用自定义组件。
在根布局上加上: xmlns:android_custom=”http://schemas.android.com/apk/res/com.example.circlepregress”http://schemas.android.com/apk/res/:自定义属性的前缀。
com.example.circlepregress:斜体部分为包名。
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android_custom="http://schemas.android.com/apk/res/com.example.circlepregress" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" > <com.example.roundprogressbar.RoundProgressBar android:id="@+id/roundProgressBar2" android:layout_width="80dip" android:layout_height="80dip" android:layout_alignLeft="@+id/roundProgressBar1" android:layout_alignParentBottom="true" android:layout_marginBottom="78dp" android_custom:roundColor="#D1D1D1" android_custom:roundProgressColor="@android:color/black" android_custom:textColor="#9A32CD" android_custom:textIsDisplayable="false" android_custom:roundWidth="10dip" android_custom:textSize="18sp"/> </RelativeLayout>
二、组合控件。
组合控件的意思就是使用原生的控件组合在一起,组成一个全新的控件。例如我们经常使用的分组标签。public class GroupLabelView extends LinearLayout { /** 简洁模式,适用于分组等标签 */ public static final int CONCISE_MODE = 0; /** 普通模式,适用于列表标签 */ public static final < 11f9a span class="hljs-keyword">int NORMAL_MODE = 1; private TextView mLeftTextView; // private View mBottomLine; private int style; public GroupLabelView(Context context) { super(context); initialize(); } public GroupLabelView(Context context, AttributeSet attrs) { super(context, attrs); initialize(); } public GroupLabelView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); initialize(); } private void initialize() { setOrientation(HORIZONTAL); // setMinimumHeight(getResources().getDimensionPixelSize(R.dimen.listview_letter_label_height)); setMinimumHeight(getResources().getDimensionPixelSize( R.dimen.people_label_view_min_height)); int paddingRight = getResources().getDimensionPixelSize( R.dimen.list_item_padding_right); int paddingLeft = getResources().getDimensionPixelSize( R.dimen.people_letter_group_label_padding_left); setPadding(paddingLeft, 0, paddingRight, 0); setBackgroundResource(R.color.people_label_background_color); mLeftTextView = new TextView(getContext()); mLeftTextView.setTextAppearance(getContext(), R.style.ContactConciseLabelTextStyle); mLeftTextView.setSingleLine(true); mLeftTextView.setGravity(Gravity.CENTER_VERTICAL); mLeftTextView.setPadding( getResources().getDimensionPixelSize( R.dimen.people_label_view_left_padding), 0, 0, 0); LinearLayout.LayoutParams textParams = new LinearLayout.LayoutParams( LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT); addView(mLeftTextView, textParams); // mBottomLine = new View(getContext()); // mBottomLine.setBackgroundColor(getResources().getColor(R.color.people_listview_label_line_color)); // LinearLayout.LayoutParams rightParams = new // LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, 1); // rightParams.gravity = Gravity.BOTTOM; // addView(mBottomLine, rightParams); } public void setLeftText(CharSequence text) { mLeftTextView.setText(text); } }
三、继承控件。
对于已有的控件,需要扩展其功能,可以使用继承控件,例如下拉刷新的ListView。1、首先定义下拉刷新的头布局:
<?xml version="1.0" encoding="utf-8"?> <!-- ListView的头部 --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" android:background="#000000" > <!-- 内容 --> <RelativeLayout android:id="@+id/head_contentLayout" android:layout_width="fill_parent" android:layout_height="wrap_content" android:paddingLeft="30dp" > <!-- 箭头图像、进度条 --> <FrameLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_centerVertical="true" > <!-- 箭头 --> <ImageView android:id="@+id/lvHeaderArrowIv" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:src="@drawable/arrow" /> <!-- 进度条 --> <ProgressBar android:id="@+id/lvHeaderProgressBar" style="?android:attr/progressBarStyleSmall" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:visibility="gone" /> </FrameLayout> <!-- 提示、最近更新 --> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:gravity="center_horizontal" android:orientation="vertical" > <!-- 提示 --> <TextView android:id="@+id/lvHeaderTipsTv" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="下拉刷新" android:textColor="@color/white" android:textSize="20sp" /> <!-- 最近更新 --> <TextView android:id="@+id/lvHeaderLastUpdatedTv" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="上次更新" android:textColor="@color/gold" android:textSize="10sp" /> </LinearLayout> </RelativeLayout> </LinearLayout>
2、然后继承ListView,实现下拉刷新。
package com.example.listview; import java.util.Date; import android.content.Context; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.animation.LinearInterpolator; import android.view.animation.RotateAnimation; import android.widget.AbsListView; import android.widget.AbsListView.OnScrollListener; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ListView; import android.widget.ProgressBar; import android.widget.TextView; public class MyListView extends ListView implements OnScrollListener { private final static int RELEASE_To_REFRESH = 0;// 下拉过程的状态值 private final static int PULL_To_REFRESH = 1; // 从下拉返回到不刷新的状态值 private final static int REFRESHING = 2;// 正在刷新的状态值 private final static int DONE = 3; private final static int LOADING = 4; // 实际的padding的距离与界面上偏移距离的比例 private final static int RATIO = 3; private LayoutInflater inflater; // ListView头部下拉刷新的布局 private LinearLayout headerView; private TextView lvHeaderTipsTv; private TextView lvHeaderLastUpdatedTv; private ImageView lvHeaderArrowIv; private ProgressBar lvHeaderProgressBar; // 定义头部下拉刷新的布局的高度 private int headerContentHeight; private RotateAnimation animation; private RotateAnimation reverseAnimation; private int startY; private int state; private boolean isBack; // 用于保证startY的值在一个完整的touch事件中只被记录一次 private boolean isRecored; private OnRefreshListener refreshListener; private boolean isRefreshable; public MyListView(Context context) { super(context); init(context); } public MyListView(Context context, AttributeSet attrs) { super(context, attrs); init(context); } private void init(Context context) { setCacheColorHint(context.getResources().getColor(R.color.transparent)); inflater = LayoutInflater.from(context); headerView = (LinearLayout) inflater.inflate(R.layout.lv_header, null); lvHeaderTipsTv = (TextView) headerView .findViewById(R.id.lvHeaderTipsTv); lvHeaderLastUpdatedTv = (TextView) headerView .findViewById(R.id.lvHeaderLastUpdatedTv); lvHeaderArrowIv = (ImageView) headerView .findViewById(R.id.lvHeaderArrowIv); // 设置下拉刷新图标的最小高度和宽度 lvHeaderArrowIv.setMinimumWidth(70); lvHeaderArrowIv.setMinimumHeight(50); lvHeaderProgressBar = (ProgressBar) headerView .findViewById(R.id.lvHeaderProgressBar); measureView(headerView); headerContentHeight = headerView.getMeasuredHeight(); // 设置内边距,正好距离顶部为一个负的整个布局的高度,正好把头部隐藏 headerView.setPadding(0, -1 * headerContentHeight, 0, 0); // 重绘一下 headerView.invalidate(); // 将下拉刷新的布局加入ListView的顶部 addHeaderView(headerView, null, false); // 设置滚动监听事件 setOnScrollListener(this); // 设置旋转动画事件 animation = new RotateAnimation(0, -180, RotateAnimation.RELATIVE_TO_SELF, 0.5f, RotateAnimation.RELATIVE_TO_SELF, 0.5f); animation.setInterpolator(new LinearInterpolator()); animation.setDuration(250); animation.setFillAfter(true); reverseAnimation = new RotateAnimation(-180, 0, RotateAnimation.RELATIVE_TO_SELF, 0.5f, RotateAnimation.RELATIVE_TO_SELF, 0.5f); reverseAnimation.setInterpolator(new LinearInterpolator()); reverseAnimation.setDuration(200); reverseAnimation.setFillAfter(true); // 一开始的状态就是下拉刷新完的状态,所以为DONE state = DONE; // 是否正在刷新 isRefreshable = false; } @Override public void onScrollStateChanged(AbsListView view, int scrollState) { } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { if (firstVisibleItem == 0) { isRefreshable = true; } else { isRefreshable = false; } } @Override public boolean onTouchEvent(MotionEvent ev) { if (isRefreshable) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: if (!isRecored) { isRecored = true; startY = (int) ev.getY();// 手指按下时记录当前位置 } break; case MotionEvent.ACTION_UP: if (state != REFRESHING && state != LOADING) { if (state == PULL_To_REFRESH) { state = DONE; changeHeaderViewByState(); } if (state == RELEASE_To_REFRESH) { state = REFRESHING; changeHeaderViewByState(); onLvRefresh(); } } isRecored = false; isBack = false; break; case MotionEvent.ACTION_MOVE: int tempY = (int) ev.getY(); if (!isRecored) { isRecored = true; startY = tempY; } if (state != REFRESHING && isRecored && state != LOADING) { // 保证在设置padding的过程中,当前的位置一直是在head,否则如果当列表超出屏幕的话,当在上推的时候,列表会同时进行滚动 // 可以松手去刷新了 if (state == RELEASE_To_REFRESH) { setSelection(0); // 往上推了,推到了屏幕足够掩盖head的程度,但是还没有推到全部掩盖的地步 if (((tempY - startY) / RATIO < headerContentHeight)// 由松开刷新状态转变到下拉刷新状态 && (tempY - startY) > 0) { state = PULL_To_REFRESH; changeHeaderViewByState(); } // 一下子推到顶了 else if (tempY - startY <= 0) {// 由松开刷新状态转变到done状态 state = DONE; changeHeaderViewByState(); } } // 还没有到达显示松开刷新的时候,DONE或者是PULL_To_REFRESH状态 if (state == PULL_To_REFRESH) { setSelection(0); // 下拉到可以进入RELEASE_TO_REFRESH的状态 if ((tempY - startY) / RATIO >= headerContentHeight) {// 由done或者下拉刷新状态转变到松开刷新 state = RELEASE_To_REFRESH; isBack = true; changeHeaderViewByState(); } // 上推到顶了 else if (tempY - startY <= 0) {// 由DOne或者下拉刷新状态转变到done状态 state = DONE; changeHeaderViewByState(); } } // done状态下 if (state == DONE) { if (tempY - startY > 0) { state = PULL_To_REFRESH; changeHeaderViewByState(); } } // 更新headView的size if (state == PULL_To_REFRESH) { headerView.setPadding(0, -1 * headerContentHeight + (tempY - startY) / RATIO, 0, 0); } // 更新headView的paddingTop if (state == RELEASE_To_REFRESH) { headerView.setPadding(0, (tempY - startY) / RATIO - headerContentHeight, 0, 0); } } break; default: break; } } return super.onTouchEvent(ev); } }
3、当状态改变时候,调用该方法,以更新界面
private void changeHeaderViewByState() { switch (state) { case RELEASE_To_REFRESH: lvHeaderArrowIv.setVisibility(View.VISIBLE); lvHeaderProgressBar.setVisibility(View.GONE); lvHeaderTipsTv.setVisibility(View.VISIBLE); lvHeaderLastUpdatedTv.setVisibility(View.VISIBLE); lvHeaderArrowIv.clearAnimation();// 清除动画 lvHeaderArrowIv.startAnimation(animation);// 开始动画效果 lvHeaderTipsTv.setText("松开刷新"); break; case PULL_To_REFRESH: lvHeaderProgressBar.setVisibility(View.GONE); lvHeaderTipsTv.setVisibility(View.VISIBLE); lvHeaderLastUpdatedTv.setVisibility(View.VISIBLE); lvHeaderArrowIv.clearAnimation(); lvHeaderArrowIv.setVisibility(View.VISIBLE); // 是由RELEASE_To_REFRESH状态转变来的 if (isBack) { isBack = false; lvHeaderArrowIv.clearAnimation(); lvHeaderArrowIv.startAnimation(reverseAnimation); lvHeaderTipsTv.setText("下拉刷新"); } else { lvHeaderTipsTv.setText("下拉刷新"); } break; case REFRESHING: headerView.setPadding(0, 0, 0, 0); lvHeaderProgressBar.setVisibility(View.VISIBLE); lvHeaderArrowIv.clearAnimation(); lvHeaderArrowIv.setVisibility(View.GONE); lvHeaderTipsTv.setText("正在刷新..."); lvHeaderLastUpdatedTv.setVisibility(View.VISIBLE); break; case DONE: headerView.setPadding(0, -1 * headerContentHeight, 0, 0); lvHeaderProgressBar.setVisibility(View.GONE); lvHeaderArrowIv.clearAnimation(); lvHeaderArrowIv.setImageResource(R.drawable.arrow); lvHeaderTipsTv.setText("下拉刷新"); lvHeaderLastUpdatedTv.setVisibility(View.VISIBLE); break; } }
最后引用下拉刷新的ListView。
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="#000000" android:orientation="vertical" > <com.example.listview.MyListView android:id="@+id/lv" android:layout_width="fill_parent" android:layout_height="fill_parent" /> </LinearLayout>
相关文章推荐
- android自定义View的用法
- android自定义控件实例
- 自定义view的自定义属性的引用
- android在自定义View的xml中设置自定义的成员属性
- 自定义android进度条
- android基础之自定义view
- 自定义View(一)
- Android自定义组件:一个波浪形的组件
- ipad开发中UIPopoverController中自定义view在Xcode6中尺寸匹配问题
- android自定义view的实现
- 自定义View步骤
- 自定义View的回调函数
- Android中的UI界面控制方式和自定义View
- Android之自定义View的封装
- Android自定义View画曲线
- 自定义View---自定义组合控件
- Android学习笔记——自定义TextView模仿验证码效果
- 最简单的实现圆形图片的方式
- Android 图片轮番 继承ViewGroup实现 可以直接使用
- 使用xib封装一个自定义view