最近项目上不忙了,马上也就国庆了,终于有那么一点点的时候可以放松了。
在gtihub上浏览的时候发现了一个雷达扫描的效果,然后自己学习了下原理,自己动手实践了下。
首先实现原理:
(1)基于Matrix 矩阵来实现雷达扫描旋转的问题
(2)基于Android里边渐变的效果来实现扫描的使用不同阶段的颜色透明到扫描颜色的过度
(3)熟练使用矩阵旋转里边的几个方法
Matrix方法中的setRotate()方法会先清除该矩阵,即设为单位矩阵。setTranslate()等方法也是一样的。所以是不能叠加各种效果在一起的。实现不了多效果。所以,如果是想多种效果同时使用的话,用postRotate(), postTranslate()。
下来就可以开始代码的编写了:
package com.example.radarviewdemo; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Shader; import android.graphics.SweepGradient; import android.os.Handler; import android.util.AttributeSet; import android.util.Log; import android.view.View; /** * Created by 郭攀峰 on 2015/8/19. */ public class RadarScanView extends View { private static final int DEFAULT_WIDTH = 300; private static final int DEFAULT_HEIGHT = 300; private int defaultWidth; private int defaultHeight; private int start; private int centerX; private int centerY; private int radarRadius; private int circleColor = Color.parseColor("#f86860"); //圆圈的颜色 private int radarColor = Color.parseColor("#99f86860"); // 雷达的颜色 private int tailColor = Color.parseColor("#50f86860"); //扫描的颜色 private Paint mPaintCircle; private Paint mPaintRadar; private Matrix matrix; private boolean isScan = true; private Handler handler = new Handler(); private Runnable run = new Runnable() { @Override public void run() { //通过这个线程来进行刷新雷达的角度 ,这样就让雷达动起来了 if (start == 360 || start > 360) { //这个是要旋转的角度,360度意思是回到了原点 start = 0; } start += 2; Log.e("start_size", start + ""); matrix = new Matrix(); matrix.postRotate(start, centerX, centerY); //旋转 postInvalidate(); //刷新界面 handler.postDelayed(run, 10); } }; public RadarScanView(Context context) { super(context); init(null, context); } public RadarScanView(Context context, AttributeSet attrs) { super(context, attrs); init(attrs, context); } public RadarScanView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(attrs, context); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); //centerX centerY 使用来确定要绘制的圆的中心点 centerX = w / 2; centerY = h / 2; radarRadius = Math.min(w, h); //半径 } private void init(AttributeSet attrs, Context context) { if (attrs != null) { TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.RadarScanView); circleColor = ta.getColor(R.styleable.RadarScanView_circleColor, circleColor); radarColor = ta.getColor(R.styleable.RadarScanView_radarColor, radarColor); tailColor = ta.getColor(R.styleable.RadarScanView_tailColor, tailColor); ta.recycle(); } initPaint(); //得到当前屏幕的像素宽高 defaultWidth = dip2px(context, DEFAULT_WIDTH); defaultHeight = dip2px(context, DEFAULT_HEIGHT); matrix = new Matrix(); handler.post(run); } /** * 设置画笔 */ private void initPaint() { mPaintCircle = new Paint(); mPaintCircle.setColor(circleColor); mPaintCircle.setAntiAlias(true);//抗锯齿 mPaintCircle.setStyle(Paint.Style.STROKE);//设置实心 mPaintCircle.setStrokeWidth(2);//画笔宽度 mPaintRadar = new Paint(); mPaintRadar.setColor(radarColor); mPaintRadar.setAntiAlias(true); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int resultWidth = 0; int modeWidth = MeasureSpec.getMode(widthMeasureSpec); int sizeWidth = MeasureSpec.getSize(widthMeasureSpec); if (modeWidth == MeasureSpec.EXACTLY) { resultWidth = sizeWidth; } else { resultWidth = defaultWidth; if (modeWidth == MeasureSpec.AT_MOST) { resultWidth = Math.min(resultWidth, sizeWidth); } } int resultHeight = 0; int modeHeight = MeasureSpec.getMode(heightMeasureSpec); int sizeHeight = MeasureSpec.getSize(heightMeasureSpec); if (modeHeight == MeasureSpec.EXACTLY) { resultHeight = sizeHeight; } else { resultHeight = defaultHeight; if (modeHeight == MeasureSpec.AT_MOST) { resultHeight = Math.min(resultHeight, sizeHeight); } } setMeasuredDimension(resultWidth, resultHeight); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //分别绘制四个圆 canvas.drawCircle(centerX, centerY, radarRadius / 7, mPaintCircle); canvas.drawCircle(centerX, centerY, radarRadius / 4, mPaintCircle); canvas.drawCircle(centerX, centerY, radarRadius / 3, mPaintCircle); canvas.drawCircle(centerX, centerY, 3 * radarRadius / 7, mPaintCircle); //设置颜色渐变从透明到不透明 Shader shader = new SweepGradient(centerX, centerY, Color.TRANSPARENT, tailColor); mPaintRadar.setShader(shader); canvas.concat(matrix); canvas.drawCircle(centerX, centerY, 3 * radarRadius / 7, mPaintRadar); } private int dip2px(Context context, float dipValue) { final float scale = context.getResources().getDisplayMetrics().density; return (int) (dipValue * scale + 0.5f); } private int px2dip(Context context, float pxValue) { final float scale = context.getResources().getDisplayMetrics().density; return (int) (pxValue / scale + 0.5f); } }
这就是雷达的效果,而里边扫描到的显示的控件下边是实现代码
package com.example.radarviewdemo; import android.animation.ObjectAnimator; import android.animation.TypeEvaluator; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.util.AttributeSet; import android.util.Log; import android.view.animation.LinearInterpolator; import android.widget.TextView; /** * Created by 10129302 on 15-2-12. */ public class RippleView extends TextView { private static final String tag = RippleView.class.getSimpleName(); private static final int DEFAULT_RIPPLE_COLOR = Color.rgb(0x33, 0x99, 0xcc); /** * 波纹的颜色 */ private int mRippleColor = DEFAULT_RIPPLE_COLOR; /** * 默认的波纹的最小值 */ private int mMinSize = 200; /** * 波纹动画效果是否正在进行 */ private boolean animationRunning = false; private int currentProgress = 0; /** * 动画中波纹的个数 */ private int mRippleNum = 4; /** * //无限长的数值,使动画不停止 */ private int mTotalTime = 1000 * 1000; public static final int MODE_IN = 1; public static final int MODE_OUT = 2; private int mode = MODE_OUT; private int mPeriod = 30; private int mCenterX; private int mCenterY; private int mRadius; private Paint mPaint; private ObjectAnimator mAnimator; public RippleView(Context context) { super(context); initPaint(); initAnimation(); } public RippleView(Context context, AttributeSet attrs) { super(context, attrs); initPaint(); initAnimation(); } public RippleView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initPaint(); initAnimation(); } private void initPaint() { mPaint = new Paint(); mPaint.setAntiAlias(true); mPaint.setStyle(Paint.Style.FILL); mPaint.setColor(mRippleColor); } private void initAnimation() { mAnimator = ObjectAnimator.ofInt(this, "currentProgress", 0, 100); mAnimator.setRepeatCount(ObjectAnimator.INFINITE); mAnimator.setRepeatMode(ObjectAnimator.RESTART); mAnimator.setInterpolator(new LinearInterpolator()); mAnimator.setEvaluator(mProgressEvaluator); mAnimator.setDuration(mTotalTime); } public void setMode(int mode) { this.mode = mode; } public void startRippleAnimation() { if (!animationRunning) { mAnimator.start(); animationRunning = true; } } public void stopRippleAnimation() { if (animationRunning) { mAnimator.end(); animationRunning = false; } } public boolean isRippleAnimationRunning() { return animationRunning; } public int getCurrentProgress() { return currentProgress; } public void setCurrentProgress(int currentProgress) { this.currentProgress = currentProgress; this.invalidate(); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //super.onMeasure(widthMeasureSpec, heightMeasureSpec); int resultWidth = 0; int modeWidth = MeasureSpec.getMode(widthMeasureSpec); int sizeWidth = MeasureSpec.getSize(widthMeasureSpec); if (modeWidth == MeasureSpec.EXACTLY) { resultWidth = sizeWidth; } else { resultWidth = mMinSize; if (modeWidth == MeasureSpec.AT_MOST) { resultWidth = Math.min(resultWidth, sizeWidth); } } int resultHeight = 0; int modeHeight = MeasureSpec.getMode(heightMeasureSpec); int sizeHeight = MeasureSpec.getSize(heightMeasureSpec); if (modeHeight == MeasureSpec.EXACTLY) { resultHeight = sizeHeight; } else { resultHeight = mMinSize; if (modeHeight == MeasureSpec.AT_MOST) { resultHeight = Math.min(resultHeight, sizeHeight); } } mCenterX = resultWidth / 2; mCenterY = resultHeight / 2; mRadius = Math.max(resultWidth, resultHeight) / 2; Log.d(tag, "ripple out view radius = " + mRadius + "; width =" + resultWidth + "; height = " + resultHeight); setMeasuredDimension(resultWidth, resultHeight); } @Override public void onDraw(Canvas canvas) { for (int i = 0; i < mRippleNum; i++) { int progress = (currentProgress + i * 100 / (mRippleNum)) % 100; if (mode == 1) progress = 100 - progress; mPaint.setAlpha(255 - 255 * progress / 100); canvas.drawCircle(mCenterX, mCenterY, mRadius * progress / 100, mPaint); } super.onDraw(canvas); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); if (isRippleAnimationRunning()) stopRippleAnimation(); } /** * 自定义估值器 */ private TypeEvaluator mProgressEvaluator = new TypeEvaluator() { @Override public Object evaluate(float fraction, Object startValue, Object endValue) { fraction = (fraction * mTotalTime / mPeriod) % 100; return fraction; } }; }
下边就是扫描到的自定义效果RandomTextView
<span style="font-family: Arial;">package com.example.radarviewdemo;</span>
import java.util.LinkedList; import java.util.Random; import java.util.Vector; import android.content.Context; import android.util.AttributeSet; import android.util.Log; import android.util.TypedValue; import android.view.Gravity; import android.view.View; import android.view.ViewTreeObserver; import android.widget.FrameLayout; public class RandomTextView extends FrameLayout implements ViewTreeObserver.OnGlobalLayoutListener { private static final String tag = RandomTextView.class.getSimpleName(); private static final int MAX = 5; private static final int IDX_X = 0; private static final int IDX_Y = 1; private static final int IDX_TXT_LENGTH = 2; private static final int IDX_DIS_Y = 3; private static final int TEXT_SIZE = 12; private Random random; private Vector<String> vecKeywords; private int width; private int height; private int mode = RippleView.MODE_OUT; private int fontColor = 0xff0000ff; private int shadowColor = 0xdd696969; public interface OnRippleViewClickListener { void onRippleViewClicked(View view); } private OnRippleViewClickListener onRippleOutViewClickListener; public RandomTextView(Context context) { super(context); init(null, context); } public RandomTextView(Context context, AttributeSet attrs) { super(context, attrs); init(attrs, context); } public RandomTextView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(attrs, context); } public void setMode(int mode) { this.mode = mode; } public void setOnRippleViewClickListener(OnRippleViewClickListener listener) { onRippleOutViewClickListener = listener; } /** * 添加RippleOutView的内容 * * @param keyword */ public void addKeyWord(String keyword) { if (vecKeywords.size() < MAX) { if (!vecKeywords.contains(keyword)) vecKeywords.add(keyword); } } public Vector<String> getKeyWords() { return vecKeywords; } public void removeKeyWord(String keyword) { if (vecKeywords.contains(keyword)) { vecKeywords.remove(keyword); } } private void init(AttributeSet attrs, Context context) { random = new Random(); vecKeywords = new Vector<String>(MAX); getViewTreeObserver().addOnGlobalLayoutListener(this); } @Override public void onGlobalLayout() { int tmpW = getWidth(); int tmpH = getHeight(); if (width != tmpW || height != tmpH) { width = tmpW; height = tmpH; Log.d(tag, "RandomTextView width = " + width + "; height = " + height); } } public void show() { this.removeAllViews(); if (width > 0 && height > 0 && vecKeywords != null && vecKeywords.size() > 0) { //找到中心点 int xCenter = width >> 1; int yCenter = height >> 1; //关键字的个数。 int size = vecKeywords.size(); int xItem = width / (size + 1); int yItem = height / (size + 1); LinkedList<Integer> listX = new LinkedList<Integer>(); LinkedList<Integer> listY = new LinkedList<Integer>(); for (int i = 0; i < size; i++) { // 准备随机候选数,分别对应x/y轴位置 listX.add(i * xItem); listY.add(i * yItem + (yItem >> 2)); } LinkedList<RippleView> listTxtTop = new LinkedList<RippleView>(); LinkedList<RippleView> listTxtBottom = new LinkedList<RippleView>(); for (int i = 0; i < size; i++) { String keyword = vecKeywords.get(i); // 随机颜色 int ranColor = fontColor; // 随机位置,糙值 int xy[] = randomXY(random, listX, listY, xItem); int txtSize = TEXT_SIZE; // 实例化RippleOutView final RippleView txt = new RippleView(getContext()); if (mode == RippleView.MODE_IN) { txt.setMode(RippleView.MODE_IN); } else { txt.setMode(RippleView.MODE_OUT); } txt.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { if (onRippleOutViewClickListener != null) onRippleOutViewClickListener.onRippleViewClicked(view); } }); txt.setText(keyword); txt.setTextColor(ranColor); txt.setTextSize(TypedValue.COMPLEX_UNIT_SP, txtSize); txt.setShadowLayer(1, 1, 1, shadowColor); txt.setGravity(Gravity.CENTER); txt.startRippleAnimation(); // 获取文本长度 //Paint paint = txt.getPaint(); int strWidth = /* (int) Math.ceil(paint.measureText(keyword)) */txt .getMeasuredWidth(); xy[IDX_TXT_LENGTH] = strWidth; // 第一次修正:修正x坐标 if (xy[IDX_X] + strWidth > width - (xItem/* >> 1 */)) { int baseX = width - strWidth; // 减少文本右边缘一样的概率 xy[IDX_X] = baseX - xItem + random.nextInt(xItem >> 1); } else if (xy[IDX_X] == 0) { // 减少文本左边缘一样的概率 xy[IDX_X] = Math.max(random.nextInt(xItem), xItem / 3); } xy[IDX_DIS_Y] = Math.abs(xy[IDX_Y] - yCenter); txt.setTag(xy); if (xy[IDX_Y] > yCenter) { listTxtBottom.add(txt); } else { listTxtTop.add(txt); } } attach2Screen(listTxtTop, xCenter, yCenter, yItem); attach2Screen(listTxtBottom, xCenter, yCenter, yItem); } } /** 修正RippleOutView的Y坐标将将其添加到容器上。 */ private void attach2Screen(LinkedList<RippleView> listTxt, int xCenter, int yCenter, int yItem) { int size = listTxt.size(); sortXYList(listTxt, size); for (int i = 0; i < size; i++) { RippleView txt = listTxt.get(i); int[] iXY = (int[]) txt.getTag(); // 第二次修正:修正y坐标 int yDistance = iXY[IDX_Y] - yCenter; // 对于最靠近中心点的,其值不会大于yItem<br/> // 对于可以一路下降到中心点的,则该值也是其应调整的大小<br/> int yMove = Math.abs(yDistance); inner : for (int k = i - 1; k >= 0; k--) { int[] kXY = (int[]) listTxt.get(k).getTag(); int startX = kXY[IDX_X]; int endX = startX + kXY[IDX_TXT_LENGTH]; // y轴以中心点为分隔线,在同一侧 if (yDistance * (kXY[IDX_Y] - yCenter) > 0) { if (isXMixed(startX, endX, iXY[IDX_X], iXY[IDX_X] + iXY[IDX_TXT_LENGTH])) { int tmpMove = Math.abs(iXY[IDX_Y] - kXY[IDX_Y]); if (tmpMove > yItem) { yMove = tmpMove; } else if (yMove > 0) { // 取消默认值。 yMove = 0; } break inner; } } } if (yMove > yItem) { int maxMove = yMove - yItem; int randomMove = random.nextInt(maxMove); int realMove = Math.max(randomMove, maxMove >> 1) * yDistance / Math.abs(yDistance); iXY[IDX_Y] = iXY[IDX_Y] - realMove; iXY[IDX_DIS_Y] = Math.abs(iXY[IDX_Y] - yCenter); // 已经调整过前i个需要再次排序 sortXYList(listTxt, i + 1); } FrameLayout.LayoutParams layParams = new FrameLayout.LayoutParams( /* FrameLayout.LayoutParams.WRAP_CONTENT */200, /* FrameLayout.LayoutParams.WRAP_CONTENT */200); layParams.gravity = Gravity.LEFT | Gravity.TOP; layParams.leftMargin = iXY[IDX_X]; layParams.topMargin = iXY[IDX_Y]; addView(txt, layParams); } } private int[] randomXY(Random ran, LinkedList<Integer> listX, LinkedList<Integer> listY, int xItem) { int[] arr = new int[4]; arr[IDX_X] = listX.remove(ran.nextInt(listX.size())); arr[IDX_Y] = listY.remove(ran.nextInt(listY.size())); return arr; } /** A线段与B线段所代表的直线在X轴映射上是否有交集。 */ private boolean isXMixed(int startA, int endA, int startB, int endB) { boolean result = false; if (startB >= startA && startB <= endA) { result = true; } else if (endB >= startA && endB <= endA) { result = true; } else if (startA >= startB && startA <= endB) { result = true; } else if (endA >= startB && endA <= endB) { result = true; } return result; } /** * 根据与中心点的距离由近到远进行冒泡排序。 * * @param endIdx 起始位置。 * @param listTxt 待排序的数组。 * */ private void sortXYList(LinkedList<RippleView> listTxt, int endIdx) { for (int i = 0; i < endIdx; i++) { for (int k = i + 1; k < endIdx; k++) { if (((int[]) listTxt.get(k).getTag())[IDX_DIS_Y] < ((int[]) listTxt .get(i).getTag())[IDX_DIS_Y]) { RippleView iTmp = listTxt.get(i); RippleView kTmp = listTxt.get(k); listTxt.set(i, kTmp); listTxt.set(k, iTmp); } } } } }
然后就是Demo:http://download.csdn.net/detail/u012808234/9146709
时间: 2024-09-22 12:50:17