自定义View之实现日出日落太阳动效

以前也很羡慕网上大神随手写写就是一个很漂亮的自定义控件,所以我下决心也要学着去写,刚好最近复习了Android View的绘制流程知识,看来看去就是那些个知识点,没点产出总感觉很迷。现在个人呢用的是华为荣耀8手机,碰巧在看自带的天气APP时,滑到最下面看到那个动效图:日出时间和日落时间上边是一个半圆,白天任意的时刻(在日出和日落时间之间)都有对应一个太阳从日出时刻沿着半圆弧做动画特效,个人第一感觉就是:就拿这个来练练手啦!于是拿着笔和纸,画了模型图,甚至求什么sin、cos函数,有点过分了哈,还得温习下三角函数。。。好,话不多说,先一睹为快:

代码传送门
实现思路:
1:首先需要绘制封闭的半圆,对应知识点:在自定义的onDraw()中拿canvas绘制;

2:其次是要设定日出和日落的时间以及当前的时间,这个必须是可以从外面配置的;

3:计算 日落时间 - 日出时间 的总分钟数,这里称为:totalMinute,再计算 当前时间 - 日出时间 的总分钟数,这里称为:currentMinute拿 currentMinute/totalMinute 计算得到 的数据保留2位小数,然后再乘以180,就能得到当前时间需要旋转的角度,因为我们画的是半圆,弧度就是180°;

4:当前时间的角度我们在第三步拿到了,那么:假如我们得到的当前时间对应的角度是60°,我们的动画就需要从0°到60°过渡执行(实际在Android上来讲,这个0°对应的是起始角度180°,为了方便描述这里假设日出对应的点在0°),在执行的过程中我们必须拿到这个60°的半圆上对应的x,y坐标点,方便我们在invalidate()更新view的时候,把小太阳不断的绘制在0~60度这个圆弧上;

5:根据第4步,将角度从0°不断地升到60°,在这其中,我们需要不断的拿每一度所对应的x,y坐标,然后把小太阳图片draw在这个位置上,因为我们知道圆的半径radius,也知道角度,角度区间是【0~60】,这个时候回去找找我们的高中数学老师,老师会告诉我们三角函数sin和cos函数,直接计算得到每一度所对应的点离圆弧底部和圆心垂直方向的绝对距离,最后算出当前角度对应的x,y坐标,为了方便理解,我也不知道这图怎么画,直接手绘了一幅,凑合看吧:

因为中间圆心的坐标我们已知,角度和半径已知,通过sin求的Y的绝对值,通过cos求得X的绝对值,然后用圆心的坐标减去求得X,Y,最终得到圆弧上各个点的坐标;
6:到了最后一步,我们直接使用动画过渡下就好了,具体的看源码去吧。。。

源码实现
思路有了,那就撸起袖子撸代码呗:源码方面的就不说太多了,也没啥好讲的,上面给了传送门,注释写的很清楚了,我就直接贴一下自定义view的代码:

SunAnimationView.java:

[java] view plain copy
public class SunAnimationView extends View
{

private int mWidth; //屏幕宽度
private int marginTop = 20;//离顶部的高度
private int mCircleColor; //圆弧颜色
private int mFontColor; //字体颜色
private int mRadius; //圆的半径

private float mCurrentAngle; //当前旋转的角度
private float mTotalMinute; //总时间(日落时间减去日出时间的总分钟数)
private float mNeedMinute; //当前时间减去日出时间后的总分钟数
private float mPercentage; //根据所给的时间算出来的百分占比
private float positionX, positionY; //太阳图片的x、y坐标
private float mFontSize; //字体颜色

private String mStartTime; //开始时间(日出时间)
private String mEndTime; //结束时间(日落时间)
private String mCurrentTime; //当前时间

private Paint mPaint; //画笔
private RectF mRectF; //半圆弧所在的矩形
private Bitmap mSunIcon; //太阳图片
private WindowManager wm;

public SunAnimationView(Context context)
{
this(context, null);
}

public SunAnimationView(Context context, @Nullable AttributeSet attrs)
{
this(context, attrs, 0);
}

public SunAnimationView(Context context, @Nullable AttributeSet attrs, int defStyleAttr)
{
super(context, attrs, defStyleAttr);
initView(context, attrs);
}

private void initView(Context context, @Nullable AttributeSet attrs)
{

TypedArray type = context.obtainStyledAttributes(attrs, R.styleable.SunAnimationView);
mCircleColor = type.getColor(R.styleable.SunAnimationView_sun_circle_color, getContext().getResources().getColor(R.color.text_black_two));
mFontColor = type.getColor(R.styleable.SunAnimationView_sun_font_color, getContext().getResources().getColor(R.color.text_black_three));
mRadius = type.getInteger(R.styleable.SunAnimationView_sun_circle_radius, DisplayUtils.dp2px(getContext(), 150));
mRadius = DisplayUtils.dp2px(getContext(), mRadius);
mFontSize = type.getDimension(R.styleable.SunAnimationView_sun_font_size, DisplayUtils.dp2px(getContext(), 12));
mFontSize = DisplayUtils.dp2px(getContext(), mFontSize);
type.recycle();

mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mSunIcon = BitmapFactory.decodeResource(getResources(), R.drawable.icon_sun);

}

public void setTimes(String startTime, String endTime, String currentTime)
{
mStartTime = startTime;
mEndTime = endTime;
mCurrentTime = currentTime;

mTotalMinute = calculateTime(mStartTime, mEndTime);//计算总时间,单位:分钟
mNeedMinute = calculateTime(mStartTime, mCurrentTime);//计算当前所给的时间 单位:分钟
mPercentage = Float.parseFloat(formatTime(mTotalMinute, mNeedMinute));//当前时间的总分钟数占日出日落总分钟数的百分比
mCurrentAngle = 180 * mPercentage;

setAnimation(0, mCurrentAngle, 5000);

}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
mWidth = wm.getDefaultDisplay().getWidth();
positionX = mWidth / 2 - mRadius - 20; // 太阳图片的初始x坐标
positionY = mRadius; // 太阳图片的初始y坐标
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom)
{
super.onLayout(changed, mWidth / 2 - mRadius, marginTop, mWidth / 2 + mRadius, mRadius * 2 + marginTop);
}

@Override
protected void onDraw(Canvas canvas)
{
//第一步:画半圆
drawSemicircle(canvas);
canvas.save();

//第二步:绘制太阳的初始位置 以及 后面在动画中不断的更新太阳的X,Y坐标来改变太阳图片在视图中的显示
drawSunPosition(canvas);

//第三部:绘制图上的文字
drawFont(canvas);

super.onDraw(canvas);
}

/**
* 绘制半圆
*/
private void drawSemicircle(Canvas canvas)
{
mRectF = new RectF(mWidth / 2 - mRadius, marginTop, mWidth / 2 + mRadius, mRadius * 2 + marginTop);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setDither(true);//防止抖动
mPaint.setColor(mCircleColor);
canvas.drawArc(mRectF, 180, 180, true, mPaint);
}

/**
* 绘制太阳的位置
*/
private void drawSunPosition(Canvas canvas)
{
canvas.drawBitmap(mSunIcon, positionX, positionY, mPaint);
}

/**
* 绘制底部左右边的日出时间和日落时间
*
* @param canvas
*/
private void drawFont(Canvas www.hbwfjx.cn/ canvas)
{
mPaint.setColor(mFontColor);
mPaint.setTextSize(mFontSize);
String startTime = TextUtils.isEmpty(mStartTime) ? "" : mStartTime;
String endTime = TextUtils.isEmpty(mEndTime) ? "" : mEndTime;
String sunrise = "日出时间:" + startTime;
String sunset = "日落时间:" + endTime;
canvas.drawText(sunrise, mWidth / 2 - mRadius, mRadius + 50 + marginTop, mPaint);
canvas.drawText(sunset, mWidth / 2 + mRadius - getTextWidth(mPaint, sunset), mRadius + 50 + marginTop, mPaint);
}

/**
* 精确计算文字宽度
*
* @param paint 画笔
* @param str 字符串文本
* @return
*/
public static int getTextWidth(Paint paint, String str)
{
int iRet = 0;
if (str != null && str.length(www.t1yl1.com/) > 0)
{
int len = str.length();
float[] widths = new float[len];
paint.getTextWidths(str, widths);
for (int j = 0; j < len; j++)
{
iRet += (int) Math.ceil(widths[j]);
}
}
return iRet;
}

/**
* 根据日出和日落时间计算出一天总共的时间:单位为分钟
*
* @param startTime 日出时间
* @param endTime 日落时间
* @return
*/
private float calculateTime(String startTime, String endTime)
{

if (checkTime(startTime, endTime))
{
String startTimes[] = startTime.split(":");
String endTimes[] = endTime.split(":");

float startHour = Float.parseFloat(startTimes[0]);
float startMinute = Float.parseFloat(startTimes[1]);

float endHour = Float.parseFloat(endTimes[0]);
float endMinute = Float.parseFloat(endTimes[1]);

float needTime = (endHour - startHour - 1) * 60 + (60 - startMinute) + endMinute;
return needTime;
}
return 0;
}

/**
* 对所给的时间做一下简单的数据校验
*
* @param startTime
* @param endTime
* @return
*/
private boolean checkTime(String startTime, String endTime)
{
if (TextUtils.isEmpty(startTime) || TextUtils.isEmpty(endTime)
|| !startTime.contains("www.yongshiyule178.com/:") || !endTime.contains(":"))
{
return false;
}

String startTimes[] = startTime.split(":");
String endTimes[] = endTime.split(":");
float startHour = Float.parseFloat(startTimes[0]);
float startMinute = Float.parseFloat(startTimes[1]);

float endHour = Float.parseFloat(endTimes[0]);
float endMinute = Float.parseFloat(endTimes[1]);

//如果所给的时间(hour)小于日出时间(hour)或者大于日落时间(hour)
if ((startHour < Float.parseFloat(mStartTime.split(":")[0]))
|| (endHour > Float.parseFloat(mEndTime.split(":")[0])))
{
return false;
}

//如果所给时间与日出时间:hour相等,minute小于日出时间
if ((startHour == Float.parseFloat(mStartTime.split(":")[0]))
&& (startMinute < Float.parseFloat(mStartTime.split(":")[1])))
{
return false;
}

//如果所给时间与日落时间:www.6788878.cn/ hour相等,minute大于日落时间
if ((startHour == Float.parseFloat(mEndTime.split(":")[0]))
&& (endMinute > Float.parseFloat(mEndTime.split(":")[1])))
{
return false;
}

if (startHour < 0 || endHour < 0
|| startHour > 23 || endHour > 23
|| startMinute < 0 || endMinute < 0
|| startMinute > 60 || endMinute > 60)
{
return false;
}
return true;
}

/**
* 根据具体的时间、日出日落的时间差值 计算出所给时间的百分占比
*
* @param totalTime 日出日落的总时间差
* @param needTime 当前时间与日出时间差
* @return
*/
private String formatTime www.niucaiyule1.cn (float totalTime, float needTime)
{
if (totalTime == 0)
return "0.00";
DecimalFormat decimalFormat = new DecimalFormat("0.00");//保留2位小数,构造方法的字符格式这里如果小数不足2位,会以0补足.
return decimalFormat.format(needTime / totalTime);//format 返回的是字符串
}

private void setAnimation(float startAngle, float currentAngle, int duration)
{
ValueAnimator sunAnimator = ValueAnimator.ofFloat(startAngle, currentAngle);
sunAnimator.setDuration(duration);
sunAnimator.setTarget(currentAngle);
sunAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener()
{
@Override
public void onAnimationUpdate(ValueAnimator animation)
{
//每次要绘制的圆弧角度
mCurrentAngle = (float) animation.getAnimatedValue();
invalidateView();
}

});
sunAnimator.start();
}

private void invalidateView()
{
//绘制太阳的x坐标和y坐标
positionX = mWidth / 2 - (float) (mRadius * Math.cos((mCurrentAngle) * Math.PI / 180)) - 20;
positionY = mRadius - www.wmyl11.com/ (float) (mRadius * Math.sin((mCurrentAngle) * Math.PI / 180)) - 10;

invalidate();
}
}
属性配置:

[html] view plain copy
<declare-styleable name="SunAnimationView">
<attr name="sun_circle_color" format="color"></attr>
<attr name="sun_font_color" format="color"></attr>
<attr name="sun_font_size" format="dimension"></attr>
<attr name="sun_circle_radius" format="integer"></attr>
</declare-styleable>
布局:

[html] view plain copy
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="www.120xh.cn/ vertical">

<com.jianzou.sunanimation.SunAnimationView
android:id="@+id/sun_view"
android:layout_width="match_parent"
android:layout_height="260dp"
app:sun_circle_color="@color/text_black_two"
app:sun_circle_radius="150"
app:sun_font_color="@color/text_black_three"
app:sun_font_size="12px"/>

<Button
android:id="@+id/btn_set_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="20dp"
android:text="设置时间"/>

</LinearLayout>
activity使用:

[java] view plain copy
public class SunAnimationActivity extends AppCompatActivity
{
Button button;
SunAnimationView sumView;

private String mCurrentTime;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sun);
initView();
}

private void initView (www.thd178.com)
{
mCurrentTime = "15:40";
sumView = (SunAnimationView) findViewById(R.id.sun_view);
button = (Button) findViewById(R.id.btn_set_time);
button.setOnClickListener(new View.OnClickListener()
{
@Override
public void www.hygjyl534.com onClick(View v)
{
sumView.setTimes("05:10", "18:40", mCurrentTime);
button.www.hjdyl.com setText("当前时间:" + mCurrentTime);
}
});
}
}
这里需要说下的是,三角函数计算得到的原点坐标有点偏差,因为我们本来就保留小数了,所以微调了下,还有一块代码看起来很蛋疼,就是对所给的时间做简单的校验。好了,自己写一写,感觉复习了很多东西,对自定义view也有了更多认识。当然,这里或许还有很多可改进的空间,有兴趣的朋友可以自己拿去改改。不过,我发现现在上传的demo选择下载积分不能为0了,最少为1积分。所以也很对不住需要下载的朋友,如果自己动手的话,上面的代码已经很全了,就是颜色和图片可以自己去整。

时间: 2024-07-30 13:40:16

自定义View之实现日出日落太阳动效的相关文章

自定义View之圆形水波扩散动效

这个效果做出来以后,真的美极了!放在你的应用中,无疑增添了光彩! 效果图    其实,第一种效果,才是产品的需求要的效果.第三种效果,是不是很熟悉?支付宝的咻一咻!哈哈,无意中,我就写出来了. 实现步骤 1.attrs.xml定义属性 <declare-styleable name="WaveView"> <!--圆颜色--> <attr name="wave_color" format="color"/> &

Android进阶:九、自定义View之手写Loading动效

这是一个很简单的动画效果,使用属性动画即可实现,希望对读者学习动画能达到抛砖引玉的效果 一.自定义动画效果--Loading效果 如上是我们需要做的一个Loading动画.Loading效果是很常见的一种动画,最简单的实现让设计画个动态图即可,或者画个静态图然后使用帧动画也可以实现.但是今天我们用纯代码实现,不用任何图片资源.大致思路我们自定义一个View,继承View类,然后画两个不同半径的弧形,转动不同的角度即可实现.绘制两个不同半径的弧形首先初始化外圆和内园的Recf(); private

自定义View之会动的闪电

前两天在csdn手机端上看到貌似是有个app具有这个闪电的loading标志,非常感兴趣,正好目前正在学习自定义view,所以就想自己来实现一下,还好自己完成了,不是很难~ 效果图: 由于是手机上截的图,所以没有弄成动态的,大家体谅一下哈. 下面是代码: 首先自定义view的属性,我先以属性的形式实现了,大家如果想做成loading的效果的话,可以自己去实现.. ``` <resources> <declare-styleable name="LightView"&g

iOS动画进阶 - 实现炫酷的上拉刷新动效

移动端访问不佳,请访问我的个人博客 最近撸了一个上拉刷新的小轮子,只要遵循一个协议就能自定义自己动效的上拉刷新和加载,我自己也写了几个动效进去,下面是一个比较好的动效的实现过程 先上效果图和github地址,有其他好的动效大家也可以交流~ 动效的原地址,在uimovement网站上看到这个动效时感觉特别6,就想自己实现一下,费了很长时间,换了几种方案终于实现出来了,下面是实现的步骤: 分析动效 写一个动效的第一步就应该仔细的去分析它,把它的每一帧展开来看,找一个最合适的方式来实现它,下面是我分析

Android自定义View效果目录

1.绚丽的loading动效的实现 2.Android自定义View:进度条+冒泡文本 3.Android雷达图(蜘蛛网图) 4.Android文本闪烁 5.Android绘制圆形进度条 6.重写TextView,实现圆形背景,文本居中显示

android动效开篇

大神博客:http://blog.csdn.net/tianjian4592/article/details/44155147 在现在的Android App开发中,动效越来越受到产品和设计师同学的重视,如此一来,也就增大了对开发同学的考验,虽说简单的动效:如移动,旋转,缩放,渐变或普通的界面跳转相对简单,但在目前日益激烈的竞争条件下,出彩复杂的动效也越来越多,并且很多效果已经无法直接用android提供的Animation或Animator框架进行实现,需要通过自定义View或ViewGrou

Android 自定义view实现水波纹效果

http://blog.csdn.net/tianjian4592/article/details/44222565 在实际的开发中,很多时候还会遇到相对比较复杂的需求,比如产品妹纸或UI妹纸在哪看了个让人兴奋的效果,兴致高昂的来找你,看了之后目的很明确,当然就是希望你能给她: 在这样的关键时候,身子板就一定得硬了,可千万别说不行,爷们儿怎么能说不行呢: 好了,为了让大家都能给妹纸们想要的,后面会逐渐分享一些比较比较不错的效果,目的只有一个,通过自定义view实现我们所能实现的动效: 今天主要分

Android应用自定义View绘制方法手册

背景 这篇迟迟难产的文章算是对2015前半年的一个交代吧,那时候有一哥们要求来一发Android Canvas相关总结,这哥们还打赏了,实在不好意思,可是这事一放就给放忘了,最近群里小伙伴催着说没更新博客,坐等更新啥的,随先有这么一篇Android应用开发超级基础的文章诞生了(因为这种文章最好写哈,就是用熟了就行).不得不说下这么久为何一直没更新博客的原因了,首先遇上了过年,我个人崇尚过节就该放下一切好好陪陪亲人,珍惜在一起的时光:其次今年开年很是蛋疼,不是不顺当就是深深的觉得被坑,所以心情也就

自定义View 篇三 《手动打造ViewPage》

有了之前自定义View的理论基础,有了ViewPage.事件分发机制.滑动冲突.Scroller使用等相关知识的铺垫,今天纯手动打造一款ViewPage. 1.完成基本的显示: 在MainActivity中: public class MainActivity extends AppCompatActivity { private MyViewPage mViewPage; int[] imageIds = new int[]{ R.drawable.pic_0, R.drawable.pic_