效果视频
前言
你还在为明天吃什么而烦恼嘛
美食大赏帮你解决选择困难症
帮你做出最佳的选择
做吃货,我们是认真的
美食大转盘
本示例使用SurfaceView绘制而成,接下来逐步分析,
文末会贴出全部代码``文末会贴出全部代码``文末会贴出全部代码
初始化SurfaceView
private void init() { mSurfaceHolder = this.getHolder(); // 管理SurfaceView的生命周期 mSurfaceHolder.addCallback(this); this.setFocusable(true); this.setFocusableInTouchMode(true); this.setKeepScreenOn(true); }
测量
通过获取高、宽,然后减去mPadding 的长度获取到控件中心点,然后存储测量的宽、高
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int width = Math.min(getMeasuredWidth(), getMeasuredHeight()); mPadding = getPaddingLeft(); mRadius = width - mPadding * 2; // 中心点 mCenter = width / 2; setMeasuredDimension(width, width); }
绘制
绘制圆盘背景
设置背景图片
private Bitmap mBgBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.turntable_bgcolor );
绘制背景突出部分
private void drawBgColor() { mCanvas.drawColor(0xFFFFFFFF); mCanvas.drawBitmap(mBgBitmap, null, new Rect(mPadding / 2, mPadding / 2, getMeasuredWidth() - mPadding / 2, getMeasuredWidth() - mPadding / 2), null); }
绘制盘块
private void drawArea() { // 起始角度 float tempAngle = mStartAngle; // 每个盘块绘制的角度 float sweepAngele = 360 / mItemCount; for (int i = 0; i < mItemCount; i++) { mArcPaint.setColor(itemColors[i]); // 绘制盘块 mCanvas.drawArc(mRange, tempAngle, sweepAngele, true, mArcPaint); // 绘制文本 drawText(tempAngle, sweepAngele, itemCharString[i]); // 绘制图标 drawIcon(tempAngle, mBitmaps[i]); tempAngle += sweepAngele; } mStartAngle += mSpeed; // 如果需要停止,让转速逐渐变小直到0 if (isShouldEnd) { mSpeed -= mDifferSpeed; } if (mSpeed <= 0) { mSpeed = 0; isShouldEnd = false; } }
绘制盘块内文字
private void drawText(float tempAngle, float sweepAngele, String itemTextStr) { Path path = new Path(); path.addArc(mRange, tempAngle, sweepAngele); // 利用水平偏移量让文字居中 float textWidth = mTextPaint.measureText(itemTextStr); int hOffset = (int) (mRadius * Math.PI / mItemCount / 2 - textWidth / 2); // 利用垂直偏移量让文字向圆心靠拢 int vOffset = mRadius / 2 / 6; mCanvas.drawTextOnPath(itemTextStr, path, hOffset, vOffset, mTextPaint); }
绘制盘块内图标
private void drawIcon(float tempAngle, Bitmap bitmap) { // 约束图片的宽度,为直径的1/8,可以作为可变参数设置 int imgWidth = mRadius / 8; // 获取弧度值 float angle = (float) ((tempAngle + 360 / mItemCount / 2) * Math.PI / 180); // 约定图片位置在直径1/4处 int x = (int) (mCenter + mRadius / 4 * Math.cos(angle)); int y = (int) (mCenter + mRadius / 4 * Math.sin(angle)); // 确定图片位置 Rect rect = new Rect(x - imgWidth / 2, y - imgWidth / 2, x + imgWidth / 2, y + imgWidth / 2); mCanvas.drawBitmap(bitmap, null, rect, null); }
开始旋转转盘
public void Start(int index) { if (isStart()) { return; } if (index < 0) { mSpeed = 50 * (1 + Math.random() * (0.5)); isShouldEnd = false; return; } }
停止旋转转盘
public void Stop() { if (isShouldEnd()) { return; } // 将初始角度重置 mStartAngle = 0; isShouldEnd = true; }
自定义转盘等份
因为我们要根据需要制作不同等份的转盘,需要跟用户产生交互,所有需要暴露一个方法供用户选择
public void InitNumber(int number){ if (number <= 0){ return; } /** * 确保为偶数*/ if (number % 2 == 0){ InitArea(number); } }
本示例以4,6,8等份为例
private void InitArea(int number){ switch (number){ case 4: fourParts(); break; case 6: sixParts(); break; case 8: eightParts(); break; default: sixParts(); } }
每一次选择转盘等份,都需要对View进行重绘,需要多次改变图片数量,所以我们将图片设置成一个公共的方法,避免内存浪费
private void fourParts(){ mItemCount = 4; itemCharString = new String[]{"粉条", "面条", "米饭", "粥",}; itemImages = new int[]{R.drawable.fen, R.drawable.mian, R.drawable.rice, R.drawable.tang}; itemColors = new int[]{0xffffc300, 0xfff17e01, 0xffffc300, 0xfff17e01}; InitImage(mItemCount,itemImages); }
private void InitImage(int count,int[] item){ mBitmaps = new Bitmap[mItemCount]; for (int i = 0; i < count; i++) { mBitmaps[i] = BitmapFactory.decodeResource(getResources(), item[i]); } }
控件引用
布局引用
XML布局文件内引用如下,其中中间的指针为图片类型
Activity 引用
默认设置8等份,如果自定义多种等份,默认必须为最大的等份,不然会出现区域空白
turntable.InitNumber( 8 );
图标切换
对开始与暂停图标进行切换,根据点击次数进行切换
public void Start(View view) { count++; /*暂停*/ if (count % 2 == 0) { turntable.Stop(); StartIcon(); } else { /*开始*/ turntable.Start( -1 ); StopIcon(); } }
更换转盘等份
首先提供三个RadioButton供用户选择,然后通过SharedPreferences
进行存储数据,有关SharedPreferences
封装的知识,请移步到另一篇博文Android SharedPreferences存取操作以及封装详解
private void SelectNumber() { RG.setOnCheckedChangeListener( new RadioGroup.OnCheckedChangeListener() { @Override public void onCheckedChanged(RadioGroup group, int checkedId) { switch (checkedId) { case R.id.fourParts: sp.PutData( context, "num", 4 ); break; case R.id.sixParts: sp.PutData( context, "num", 6 ); break; case R.id.eightParts: sp.PutData( context, "num", 8 ); break; } } } ); }
然后取出数据,并回调等份数值即可
public void Confirm(View view) { RevealAnim( view ); loadingView.setVisibility( View.VISIBLE ); startLoading(); startPercentMockThread(); int num = (int) sp.GetData( context, "num", 0 ); turntable.InitNumber( num ); }
沉浸式体验
效果图
沉浸式体验即标题栏与系统状态栏主题一致我们分为以下几个步骤完成以上效果
建立一个样式
首先我们需要建立一个标题栏为空的样式,我们有两种方式去实现
第一种,使用系统提供样式
第二种,直接使用标志进行设置
其实第一种的源码就是第二种,(废话文学)
引用空标题栏样式
我们需要在我们要隐藏标题栏的Activity中引用如下代码android:theme="@style/NotActionBar"
配置图如下,地址:清单文件中进行配置
自定义标题栏
我们隐藏了标题栏之后,我们需要自定义一个自己喜欢的风格的标题栏
推荐使用ToolBar
,本示例为了简单,就直接使用了TextView,代码如下
合二为一
终于到了最后一步,我们需要在设置Activity对系统栏进行设置
首先需要对版本进行一个判定,防止版本不兼容
其次获取DecorView实例
然后使用标志符对系统状态进行设置,其中以下两个系统标志符为全屏和隐藏系统状态栏
重中之重
,以下代码必须在setContentView( R.layout.activity_main );
之前执行
重中之重
,以下代码必须在setContentView( R.layout.activity_main );
之前执行
重中之重
,以下代码必须在setContentView( R.layout.activity_main );
之前执行
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN和View.SYSTEM_UI_FLAG_LAYOUT_STABLE
protected void onCreate(Bundle savedInstanceState) { super.onCreate( savedInstanceState ); if (Build.VERSION.SDK_INT >= 21) { View decorView = getWindow().getDecorView(); decorView.setSystemUiVisibility( View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE ); getWindow().setStatusBarColor( Color.TRANSPARENT ); } setContentView( R.layout.activity_main ); InitView(); SelectNumber(); }
Reveal Animator
效果视频
建立一个圆形样式
首先我们需要建立一个圆形样式,在res->drawable下建立一个oval.xml文件
代码如下:
动画设置
在XML文件中配置我们建立的圆形样式
android:background="@drawable/oval"
然后再Activity中进行动画设置,其中createCircularReveal()
方法的五个参数分别为:控件,动画开始的中心的X,动画开始的中心的Y,动画开始的半径,动画结束的半径
private void RevealAnim(View view) { Animator animator = ViewAnimationUtils.createCircularReveal( view, view.getWidth() / 2, view.getHeight() / 2, view.getWidth(), 0 ); animator.setInterpolator( new AccelerateDecelerateInterpolator() ); animator.setDuration( 2000 ); animator.start(); }
自定义转盘代码
public class Turntable extends SurfaceView implements SurfaceHolder.Callback, Runnable { private SurfaceHolder mSurfaceHolder; private Canvas mCanvas; /** * 用于SurfaceView绘制的子线程 */ private Thread mThread; /** * 控制子线程开关 */ private boolean isRunning; /** * 字样*/ private String[] itemCharString; /** * 图片*/ private int[] itemImages; private Bitmap[] mBitmaps; /** * 背景 */ private Bitmap mBgBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.turntable_bgcolor ); /** * 色块*/ private int[] itemColors; /** * 默认等份*/ private int mItemCount = 8; /** * 整个盘块的范围 */ private RectF mRange = new RectF(); /** * 整个盘块的直径 */ private int mRadius; /** * 绘制盘块的画笔 */ private Paint mArcPaint; /** * 绘制文本的画笔 */ private Paint mTextPaint; /** * 字体大小*/ private float mTextSize = TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_SP, 20, getResources().getDisplayMetrics()); /** * 盘块滚动的速度 */ private double mSpeed = 0; /** * 转盘的中心位置 */ private int mCenter; /** * 这里我们的padding直接取paddingLeft */ private int mPadding; /** * volatile保证线程间的可见性 */ private volatile float mStartAngle = 0; /** * 判断是否点击了停止按钮 */ private boolean isShouldEnd = false; /** * 设置单次绘制最低时间,如果在该时间内绘制完成,让子线程sleep到改时间结束 * 这样防止了线程绘制频繁,先消耗性能的问题 */ private long mOneTimeMinMillionSeconds = 50; private int mDifferSpeed = 1;// 调用停止后递减的速度差值 要大于0 public Turntable(Context context) { super(context); init(); } public Turntable(Context context, AttributeSet attrs) { super(context, attrs); init(); } public Turntable(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } public void InitNumber(int number){ if (number <= 0){ return; } /** * 确保为偶数*/ if (number % 2 == 0){ InitArea(number); } } private void InitArea(int number){ switch (number){ case 4: fourParts(); break; case 6: sixParts(); break; case 8: eightParts(); break; default: sixParts(); } } private void fourParts(){ mItemCount = 4; itemCharString = new String[]{"粉条", "面条", "米饭", "粥",}; itemImages = new int[]{R.drawable.fen, R.drawable.mian, R.drawable.rice, R.drawable.tang}; itemColors = new int[]{0xffffc300, 0xfff17e01, 0xffffc300, 0xfff17e01}; InitImage(mItemCount,itemImages); } private void sixParts(){ mItemCount = 6; itemCharString = new String[]{"火锅", "汉堡", "巧克力", "奶茶", "蛋糕", "炸鸡"}; itemImages = new int[]{R.drawable.huoguo, R.drawable.hanbao, R.drawable.qiaokeli, R.drawable.naicha, R.drawable.dangao, R.drawable.zhaji1}; itemColors = new int[]{0xffffc300, 0xfff17e01, 0xffffc300, 0xfff17e01, 0xffffc300, 0xfff17e01}; InitImage(mItemCount,itemImages); } private void eightParts(){ mItemCount = 8; itemCharString = new String[]{"苹果", "香蕉", "榴莲", "西瓜", "葡萄", "火龙果","芒果","草莓"}; itemImages = new int[]{R.drawable.apple, R.drawable.xaingjiao, R.drawable.liulian, R.drawable.xigua, R.drawable.putao, R.drawable.huolongguo,R.drawable.mangguo,R.drawable.caomei}; itemColors = new int[]{0xffffc300, 0xfff17e01, 0xffffc300, 0xfff17e01, 0xffffc300, 0xfff17e01,0xffffc300,0xfff17e01}; InitImage(mItemCount,itemImages); } private void init() { mSurfaceHolder = this.getHolder(); // 管理SurfaceView的生命周期 mSurfaceHolder.addCallback(this); // 能够获取焦点 this.setFocusable(true); this.setFocusableInTouchMode(true); // 保持常亮 this.setKeepScreenOn(true); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int width = Math.min(getMeasuredWidth(), getMeasuredHeight()); mPadding = getPaddingLeft(); mRadius = width - mPadding * 2; // 中心点 mCenter = width / 2; setMeasuredDimension(width, width); } private void InitImage(int count,int[] item){ mBitmaps = new Bitmap[mItemCount]; for (int i = 0; i < count; i++) { mBitmaps[i] = BitmapFactory.decodeResource(getResources(), item[i]); } } @Override public void surfaceCreated(SurfaceHolder surfaceHolder) { // 初始化盘块画笔 mArcPaint = new Paint(); mArcPaint.setAntiAlias(true); mArcPaint.setDither(true); // 初始化文字画笔 mTextPaint = new Paint(); mTextPaint.setColor(0xffffffff); mTextPaint.setTextSize(mTextSize); // 初始化盘块绘制范围 mRange = new RectF(mPadding, mPadding, mPadding + mRadius, mPadding + mRadius); // 初始化图片 InitImage(mItemCount,itemImages); isRunning = true; mThread = new Thread(this); mThread.start(); } @Override public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) { } @Override public void surfaceDestroyed(SurfaceHolder surfaceHolder) { isRunning = false; } @Override public void run() { /** * 不断的进行绘制 */ while (isRunning) { long preMillions = System.currentTimeMillis(); draw(); long afterMillions = System.currentTimeMillis(); long drawOnceTime = afterMillions - preMillions; if (drawOnceTime < mOneTimeMinMillionSeconds) { try { Thread.sleep(mOneTimeMinMillionSeconds - drawOnceTime); } catch (InterruptedException e) { e.printStackTrace(); } } } } private void draw() { try { mCanvas = mSurfaceHolder.lockCanvas(); if (mCanvas != null) { /* 绘制背景颜色*/ drawBgColor(); /*绘制区域*/ drawArea(); } } catch (Exception e) { e.printStackTrace(); } finally { if (mCanvas != null) { mSurfaceHolder.unlockCanvasAndPost(mCanvas); } } } /** * 绘制盘块 */ private void drawArea() { // 起始角度 float tempAngle = mStartAngle; // 每个盘块绘制的角度 float sweepAngele = 360 / mItemCount; for (int i = 0; i < mItemCount; i++) { mArcPaint.setColor(itemColors[i]); // 绘制盘块 mCanvas.drawArc(mRange, tempAngle, sweepAngele, true, mArcPaint); // 绘制文本 drawText(tempAngle, sweepAngele, itemCharString[i]); // 绘制图标 drawIcon(tempAngle, mBitmaps[i]); tempAngle += sweepAngele; } mStartAngle += mSpeed; // 如果需要停止,让转速逐渐变小直到0 if (isShouldEnd) { mSpeed -= mDifferSpeed; } if (mSpeed <= 0) { mSpeed = 0; isShouldEnd = false; } } /** * 绘制每个盘块的图标 * * @param tempAngle * @param bitmap */ private void drawIcon(float tempAngle, Bitmap bitmap) { // 约束图片的宽度,为直径的1/8,可以作为可变参数设置 int imgWidth = mRadius / 8; // 获取弧度值 float angle = (float) ((tempAngle + 360 / mItemCount / 2) * Math.PI / 180); // 约定图片位置在直径1/4处 int x = (int) (mCenter + mRadius / 4 * Math.cos(angle)); int y = (int) (mCenter + mRadius / 4 * Math.sin(angle)); // 确定图片位置 Rect rect = new Rect(x - imgWidth / 2, y - imgWidth / 2, x + imgWidth / 2, y + imgWidth / 2); mCanvas.drawBitmap(bitmap, null, rect, null); } /** * 绘制每个盘块的文本 * * @param tempAngle * @param sweepAngele * @param itemTextStr */ private void drawText(float tempAngle, float sweepAngele, String itemTextStr) { Path path = new Path(); path.addArc(mRange, tempAngle, sweepAngele); // 利用水平偏移量让文字居中 float textWidth = mTextPaint.measureText(itemTextStr); int hOffset = (int) (mRadius * Math.PI / mItemCount / 2 - textWidth / 2); // 利用垂直偏移量让文字向圆心靠拢 int vOffset = mRadius / 2 / 6; mCanvas.drawTextOnPath(itemTextStr, path, hOffset, vOffset, mTextPaint); } /** * 绘制背景 */ private void drawBgColor() { mCanvas.drawColor(0xFFFFFFFF); mCanvas.drawBitmap(mBgBitmap, null, new Rect(mPadding / 2, mPadding / 2, getMeasuredWidth() - mPadding / 2, getMeasuredWidth() - mPadding / 2), null); } /** * 启动转盘 * 能够控制到具体某个index范围内停止 */ public void Start(int index) { if (isStart()) { return; } if (index < 0) { mSpeed = 50 * (1 + Math.random() * (0.5)); isShouldEnd = false; return; } } /** * 停止转盘 */ public void Stop() { if (isShouldEnd()) { return; } // 将初始角度重置 mStartAngle = 0; isShouldEnd = true; } /** * 转盘是否在旋转 * * @return */ public boolean isStart() { return mSpeed != 0; } /** * 是否停止状态(但可能处于旋转减速到停止) * * @return */ public boolean isShouldEnd() { return isShouldEnd; } }
XML布局代码
Activity代码
public class MainActivity extends AppCompatActivity { private AnimatedCircleLoadingView loadingView; private Turntable turntable; private int count = 0; private ImageView ChangeStatus; private RadioGroup RG; private SP sp; private Context context = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate( savedInstanceState ); if (Build.VERSION.SDK_INT >= 21) { View decorView = getWindow().getDecorView(); decorView.setSystemUiVisibility( View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE ); getWindow().setStatusBarColor( Color.TRANSPARENT ); } setContentView( R.layout.activity_main ); InitView(); SelectNumber(); } private void InitView() { turntable = findViewById( R.id.TurnTable ); loadingView = findViewById( R.id.loadingView ); ChangeStatus = findViewById( R.id.StartAndEnd ); RG = findViewById( R.id.RG ); /*默认设置8等份*/ turntable.InitNumber( 8 ); if (context == null) { context = MainActivity.this; } sp = new SP( context ); } public void Start(View view) { count++; /*暂停*/ if (count % 2 == 0) { turntable.Stop(); StartIcon(); } else { /*开始*/ turntable.Start( -1 ); StopIcon(); } } private void StartIcon() { ChangeStatus.setImageDrawable( getResources().getDrawable( R.drawable.start ) ); } private void StopIcon() { ChangeStatus.setImageDrawable( getResources().getDrawable( R.drawable.stop ) ); } public void Confirm(View view) { RevealAnim( view ); loadingView.setVisibility( View.VISIBLE ); startLoading(); startPercentMockThread(); int num = (int) sp.GetData( context, "num", 0 ); turntable.InitNumber( num ); } public void Reset(View view) { RevealAnim( view ); loadingView.setVisibility( View.GONE ); resetLoading(); } private void startLoading() { loadingView.startIndeterminate(); } private void startPercentMockThread() { Runnable runnable = new Runnable() { @Override public void run() { try { Thread.sleep( 500 ); for (int i = 0; i <= 100; i++) { Thread.sleep( 40 ); changePercent( i ); } } catch (InterruptedException e) { e.printStackTrace(); } } }; new Thread(runnable).start(); } private void changePercent(final int percent) { runOnUiThread( new Runnable() { @Override public void run() { loadingView.setPercent( percent ); } } ); } public void resetLoading() { runOnUiThread( new Runnable() { @Override public void run() { loadingView.resetLoading(); } } ); } private void RevealAnim(View view) { Animator animator = ViewAnimationUtils.createCircularReveal( view, view.getWidth() / 2, view.getHeight() / 2, view.getWidth(), 0 ); animator.setInterpolator( new AccelerateDecelerateInterpolator() ); animator.setDuration( 2000 ); animator.start(); } private void SelectNumber() { RG.setOnCheckedChangeListener( new RadioGroup.OnCheckedChangeListener() { @Override public void onCheckedChanged(RadioGroup group, int checkedId) { switch (checkedId) { case R.id.fourParts: sp.PutData( context, "num", 4 ); break; case R.id.sixParts: sp.PutData( context, "num", 6 ); break; case R.id.eightParts: sp.PutData( context, "num", 8 ); break; } } } ); } }
代码下载地址
gitee下载地址
到此这篇关于Android 美食大转盘详解流程的文章就介绍到这了,更多相关Android 美食大转盘内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!