自定义view教你如何打造圆形的seekbar


转载请标明出处:
http://blog.csdn.net/liuzg1220;
本文出自:【HugeBug的博客】

简介

seekBar大家都不陌生吧,圆形的seekbar不知道你有没有见过,见过的你又没有写过呢?

几年的一个项目,老板对我说android 自带的seekbar太丑了,让我写一个圆的,我想了想觉得这个难不倒我,于是就试着写了一个。先看效果图:


分析

首先这是一个自定的view,这是毋庸置疑的。这个自定义的view怎么写呢?根据上图的观察,我们看到外面的一个圆环,圆环里面有一个用来显示进度的textView。OK,带着这样的思路我们思考一下如何实现。

第一件事我能想到的是画两个同心圆,两个圆的半径不一样。产生一个圆环。

第二件要做的事我们要根据手指滑动位置画不同颜色的圆弧,这样就可以产生进度条的效果了。

第三件事我们要在中间的显示具体的数值,事实上具体的数字这里还有一个圆形的背景图。

分析完了,不扯蛋,直接上代码:

代码实现

自定义的MCircleSeekBar :

<span style="font-size:14px;">package com.lzg.circleseekbar.widget;

/**
 * @author lzg
 * 2014/4/13
 */
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

import com.lzg.circleseekbar.R;

/**
 * The Class CircularSeekBar.
 */
public class MCircleSeekBar extends View {
	/** The context */
	private Context mContext;
	/** seekbar变化监听 */
	private OnSeekChangeListener mListener;
	/** 画圆环的paint */
	private Paint circleColor;
	/** 画内圆的paint */
	private Paint innerColor;
	/** 圆环的Paint,其实是画弧形 */
	private Paint circleRing;
	/** 移动之后的角度 */
	private int angle = 0;
	/** 12点位置的开始角度 */
	private int startAngle = 270;
	/** 圆环宽度 */
	private int barWidth = 30;
	/** view的宽 */
	private int width;
	/** view的高 */
	private int height;
	/** seekbar总值 */
	private int maxProgress = 100;
	/** 当前的百分值 */
	private int progress;
	/** 百分值 */
	private int progressPercent;
	/** 内圆半径 */
	private float innerRadius;
	/** 外圆半径 */
	private float outerRadius;
	/** 圆心X坐标 */
	private float cx;
	/** 圆心Y坐标 */
	private float cy;
	/** 画圆图层左边边距 */
	private float left;
	/** 画圆图层右边边距 */
	private float right;
	/** T画圆图层顶端边距 */
	private float top;
	/** 画圆图层底边边距 */
	private float bottom;
	/** 背景图左边边距 */
	private float bgLeft;
	/** 背景图右边边距 */
	private float bgRight;
	/** 背景图顶端边距 */
	private float bgTop;
	/** 背景图底边边距 */
	private float bgBottom;
	/** progressMark X坐标 */
	private float dx;
	/** progressMark Y坐标 */
	private float dy;
	/** 12点钟位置的X坐标 */
	private float startPointX;
	/** 12点钟位置的Y坐标 */
	private float startPointY;
	/**
	 * 标记的seekbar当前位置的X坐标,预设置值为12点位置
	 */
	private float markPointX;
	/**
	 * T标记的seekbar当前位置的Y坐标,预设置值为12点位置
	 */
	private float markPointY;
	/**
	 * 进度调整指数
	 */
	private float adjustmentFactor = 100;
	/** 正常状态下的进度条点 */
	private Bitmap progressMark;
	/** 手指按下后的进度条点 */
	private Bitmap progressMarkPressed;
	/**
	 * 中间背景图
	 */
	private Bitmap progressBg;

	/** 是否手指按下的标志位 */
	private boolean IS_PRESSED = false;
	/**
	 * 绘制圆中间的背景图的Paint
	 */
	private Paint paint = null;
	private Canvas canvas = new Canvas();
	/** 画圆图层. */
	private RectF rect = new RectF();
	/** 背景图层 */
	RectF rectBg = new RectF();
	{
		mListener = new OnSeekChangeListener() {
			@Override
			public void onProgressChange(MCircleSeekBar view, int newProgress) {
			}
		};
		/*
		 * Paint.ANTI_ALIAS_FLAG为抗锯齿
		 */
		paint = new Paint(Paint.ANTI_ALIAS_FLAG);
		circleColor = new Paint();
		innerColor = new Paint();
		circleRing = new Paint();
		/*
		 * 设置外圆的颜色为蓝色
		 */
		circleColor.setColor(Color.GRAY);// Set default background color to Gray
		/*
		 * 设置内圆的颜色
		 */
		innerColor.setColor(Color.parseColor("#313131"));
		/*
		 * 设置圆环的颜色
		 */
		circleRing.setColor(Color.parseColor("#ff33b5e5"));
		/*
		 * Paint.ANTI_ALIAS_FLAG为抗锯齿
		 */
		circleColor.setAntiAlias(true);
		innerColor.setAntiAlias(true);
		circleRing.setAntiAlias(true);
		circleColor.setStrokeWidth(5);
		innerColor.setStrokeWidth(5);
		circleRing.setStrokeWidth(5);
		circleRing.setStyle(Paint.Style.FILL);
	}

	/**
	 * MCircleSeekBar的构造方法.
	 * 
	 * @param context
	 * @param attrs
	 * @param defStyle
	 */
	public MCircleSeekBar(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		mContext = context;
		initDrawable();
	}

	/**
	 * MCircleSeekBar的构造方法.
	 * 
	 * @param context
	 * @param attrs
	 */
	public MCircleSeekBar(Context context, AttributeSet attrs) {
		super(context, attrs);
		mContext = context;
		initDrawable();
	}

	/**
	 * MCircleSeekBar的构造方法.
	 * 
	 * @param context
	 */
	public MCircleSeekBar(Context context) {
		super(context);
		mContext = context;
		initDrawable();
	}

	/**
	 * 初始化图片
	 */
	public void initDrawable() {
		progressMark = BitmapFactory.decodeResource(mContext.getResources(),
				R.drawable.seek_bar);
		progressMarkPressed = BitmapFactory.decodeResource(
				mContext.getResources(), R.drawable.seek_bar1);
		progressBg = BitmapFactory.decodeResource(mContext.getResources(),
				R.drawable.open_perecent_bg);
	}

	/*
	 * 重写view的计算方法
	 * 
	 * @see android.view.View#onMeasure(int, int)
	 */
	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
		width = getWidth(); // Get View Width
		height = getHeight();// Get View Height
		int size = (width > height) ? height : width; // Choose the smaller
		cx = width / 2; // Center X for circle
		cy = height / 2; // Center Y for circle
		outerRadius = size / 2 * 80 / 100; // Radius of the outer circle
		innerRadius = outerRadius - barWidth; // Radius of the inner circle
		left = cx - outerRadius; // Calculate left bound of our rect
		right = cx + outerRadius;// Calculate right bound of our rect
		top = cy - outerRadius;// Calculate top bound of our rect
		bottom = cy + outerRadius;// Calculate bottom bound of our rect
		bgLeft = cx - innerRadius;
		bgRight = cx + innerRadius;
		bgTop = cy - innerRadius;
		bgBottom = cy + innerRadius;

		startPointX = getXByProgress(progress);
		startPointY = getYByProgress(progress);
		markPointX = startPointX;// Initial locatino of the marker X coordinate
		markPointY = startPointY;// Initial locatino of the marker Y coordinate
		rect.set(left, top, right, bottom); // assign size to rect
		rectBg.set(bgLeft, bgTop, bgRight, bgBottom);
	}

	/**
	 * 
	 */
	@Override
	protected void onDraw(Canvas canvas) {
		/*
		 * 画外圆
		 */
		canvas.drawCircle(cx, cy, outerRadius, circleColor);
		/*
		 * 画圆弧
		 */
		canvas.drawArc(rect, startAngle, angle, true, circleRing);
		/*
		 * 画内圆
		 */
		canvas.drawCircle(cx, cy, innerRadius, innerColor);
		/*
		 * 画seek点
		 */
		canvas.drawBitmap(progressBg, null, rectBg, paint);
		/*
		 * 获取seek点X坐标
		 */
		dx = getXFromAngle();
		/*
		 * 获取seek点Y坐标
		 */
		dy = getYFromAngle();
		/*
		 * 绘制seek点的位置
		 */
		drawMarkerAtProgress(canvas);
		super.onDraw(canvas);
	}

	/**
	 * 画progressMark(seek)在圆环上的位置.
	 * 
	 * @param canvas
	 */
	public void drawMarkerAtProgress(Canvas canvas) {
		if (IS_PRESSED) {
			canvas.drawBitmap(progressMarkPressed, dx, dy, null);
		} else {
			canvas.drawBitmap(progressMark, dx, dy, null);
		}
	}

	/**
	 * 获取seek点X坐标
	 * 
	 * @return
	 */
	public float getXFromAngle() {
		int size1 = progressMark.getWidth();
		int size2 = progressMarkPressed.getWidth();
		int adjust = (size1 > size2) ? size1 : size2;
		float x = markPointX - (adjust / 2);
		return x;
	}

	/**
	 * 获取seek点Y坐标
	 * 
	 * @return
	 */
	public float getYFromAngle() {
		int size1 = progressMark.getHeight();
		int size2 = progressMarkPressed.getHeight();
		int adjust = (size1 > size2) ? size1 : size2;
		float y = markPointY - (adjust / 2);
		return y;
	}

	/**
	 * 获取圆弧角度
	 * 
	 * @return the angle
	 */
	public int getAngle() {
		return angle;
	}

	/**
	 * 设置圆弧的角度
	 * 
	 * @param angle
	 */
	public void setAngle(int angle) {
		this.angle = angle;
		float donePercent = (((float) this.angle) / 360) * 100;
		float progress = (donePercent / 100) * getMaxProgress();
		setProgressPercent(Math.round(donePercent));
		setProgress(Math.round(progress));
	}

	/**
	 * 设置seekbar变化的监听
	 * 
	 * @param listener
	 */
	public void setSeekBarChangeListener(OnSeekChangeListener listener) {
		mListener = listener;
	}

	/**
	 * 获取seekbar变化的监听
	 * 
	 * @return the seek bar change listener
	 */
	public OnSeekChangeListener getSeekBarChangeListener() {
		return mListener;
	}

	/**
	 * 获得bar宽度
	 * 
	 * @return the bar width
	 */
	public int getBarWidth() {
		return barWidth;
	}

	/**
	 * 设置bar宽度
	 * 
	 * @param barWidth
	 */
	public void setBarWidth(int barWidth) {
		this.barWidth = barWidth;
	}

	/**
	 * 定义seekbar变化监听接口
	 * 
	 * @see OnSeekChangeEvent
	 */
	public interface OnSeekChangeListener {
		/**
		 * On progress change.
		 * 
		 * @param view
		 * @param newProgress
		 */
		public void onProgressChange(MCircleSeekBar view, int newProgress);
	}

	/**
	 * Gets the max progress.
	 * 
	 * @return the max progress
	 */
	public int getMaxProgress() {
		return maxProgress;
	}

	/**
	 * 
	 * @param maxProgress
	 * 
	 */
	public void setMaxProgress(int maxProgress) {
		this.maxProgress = maxProgress;
	}

	/**
	 * 
	 * @return the progress
	 */
	public int getProgress() {
		return progress;
	}

	/**
	 * 
	 * @param progress
	 * 
	 */
	public void setProgress(int progress) {
		this.progress = progress;
		if (!IS_PRESSED) {
			int newPercent = (this.progress * 100) / this.maxProgress;
			int newAngle = (newPercent * 360) / 100;
			this.setAngle(newAngle);
			this.setProgressPercent(newPercent);
		}
		mListener.onProgressChange(this, this.getProgress());
	}

	/**
	 * 
	 * @return
	 */
	public int getProgressPercent() {
		return progressPercent;
	}

	/**
	 * 
	 * @param progressPercent
	 */
	public void setProgressPercent(int progressPercent) {
		this.progressPercent = progressPercent;
	}

	/**
	 * 
	 * @param color
	 */
	public void setRingBackgroundColor(int color) {
		circleRing.setColor(color);
	}

	/**
	 * 
	 * @param color
	 */
	public void setBackGroundColor(int color) {
		innerColor.setColor(color);
	}

	/**
	 * 
	 * @param color
	 */
	public void setProgressColor(int color) {
		circleRing.setColor(color);
	}

	/**
	 * 重写onTouch方法
	 */
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		float x = event.getX();
		float y = event.getY();
		boolean up = false;
		switch (event.getAction()) {
		case MotionEvent.ACTION_DOWN:
			moved(x, y, up);
			break;
		case MotionEvent.ACTION_MOVE:
			moved(x, y, up);
			break;
		case MotionEvent.ACTION_UP:
			up = true;
			moved(x, y, up);
			break;
		}
		return true;
	}

	/**
	 * seek点移动方法
	 * 
	 * @param x
	 *            the x
	 * @param y
	 *            the y
	 * @param up
	 *            the up
	 */
	private void moved(float x, float y, boolean up) {
		float distance = (float) Math.sqrt(Math.pow((x - cx), 2)
				+ Math.pow((y - cy), 2));
		if (distance < outerRadius + adjustmentFactor
				&& distance > innerRadius - adjustmentFactor && !up) {
			IS_PRESSED = true;

			/*
			 * 根据三角函数整切定理计算得到X.Y的坐标
			 */
			markPointX = (float) (cx + outerRadius
					* Math.cos(Math.atan2(x - cx, cy - y) - (Math.PI / 2)));
			markPointY = (float) (cy + outerRadius
					* Math.sin(Math.atan2(x - cx, cy - y) - (Math.PI / 2)));
			float degrees = (float) ((float) ((Math.toDegrees(Math.atan2(
					x - cx, cy - y)) + 360.0)) % 360.0);
			if (degrees < 0) {
				degrees += 2 * Math.PI;
			}
			setAngle(Math.round(degrees));
			invalidate();
		} else {
			IS_PRESSED = false;
			invalidate();
		}
	}

	public float getXByProgress(int progress) {
		float x = 0;
		float angle = (float) (2 * progress * Math.PI / 100);
		x = (float) (cx + outerRadius * Math.cos(angle - Math.PI / 2));
		return x;
	}

	public float getYByProgress(int progress) {
		float y = 0;
		float angle = (float) (2 * progress * Math.PI / 100);
		y = (float) (cy + outerRadius * Math.sin(angle - Math.PI / 2));
		return y;
	}

	public void setMarkPointXY(int progress) {
		this.progress = progress;
	}

	/**
	 * 
	 * @return
	 */
	public float getAdjustmentFactor() {
		return adjustmentFactor;
	}

	/**
	 * 
	 * @param adjustmentFactor
	 */
	public void setAdjustmentFactor(float adjustmentFactor) {
		this.adjustmentFactor = adjustmentFactor;
	}

}</span>
布局文件activity_main.xml:

<span style="font-size:14px;"><FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:layout_gravity="center_horizontal"
    android:background="#e0313131"
    tools:context=".MainActivity" >
        <com.lzg.circleseekbar.widget.MCircleSeekBar
            android:id="@+id/m_circleSeekBar_set_perencet"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center" />

    <RelativeLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center" >

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:orientation="horizontal" >

            <TextView
                android:id="@+id/tv_perencet_set_perencet"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="0"
                android:textColor="#ffffff"
                android:textSize="60sp" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="%"
                android:textColor="#ffffff"
                android:textSize="40sp" />
        </LinearLayout>
    </RelativeLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom"
        android:orientation="vertical" >

        <TextView style="@style/line_text_style" />

    </LinearLayout>

</FrameLayout></span>

BaseActivity:

<span style="font-size:14px;">package com.lzg.circleseekbar;

import android.app.Activity;
import android.view.View;

/**
 * 
 * @author lzg
 *
 */
public class BaseActivity extends Activity {

	/**
	 * 解决强制类型转换的问题
	 * 
	 * @param id
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public <T extends View> T getViewById(int id) {
		return (T) findViewById(id);
	}

}</span>
界面调用的主MainAcitivity:

<span style="font-size:14px;">package com.lzg.circleseekbar;

import android.os.Bundle;
import android.widget.TextView;

import com.lzg.circleseekbar.widget.MCircleSeekBar;
import com.lzg.circleseekbar.widget.MCircleSeekBar.OnSeekChangeListener;

/**
 * 
 * @author lzg
 *
 */
public class MainActivity extends BaseActivity {
	private TextView tvPerencetValue;
	private MCircleSeekBar mCircleSeekBar;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		tvPerencetValue = getViewById(R.id.tv_perencet_set_perencet);
		mCircleSeekBar = getViewById(R.id.m_circleSeekBar_set_perencet);

		mCircleSeekBar.setSeekBarChangeListener(new OnSeekChangeListener() {

			public void onProgressChange(MCircleSeekBar view, int newProgress) {
				tvPerencetValue.setText(Integer.toString(view.getProgress()));
			}
		});
	}

}</span>

到此代码全部结束了!

是不是感觉代码有点多,思路清晰了,也就水到渠成了。。。


如果你觉得有用就关注我吧,在这里将不定期的发表原创文章!如果你觉得有用就留个言,点个赞吧!

下面是我微信公众号:如果你喜欢就扫扫看吧!


源码下载


  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值