[转载]Android Metro风格的Launcher开发系列第三篇 - wuhao_blog - 博客园

mikel阅读(1417)

[转载]Android Metro风格的Launcher开发系列第三篇 – wuhao_blog – 博客园.

前言:

各位小伙伴,又到了每周更新文章了时候了,本来是周日能发出来呢,这不是赶上清明节吗,女王大人发话了,清明节前两天半陪她玩,只留给我周一下午半天时间写博客快哭了,哪里有女王哪里就有压迫呀有木有!好了闲话少说,上一篇博客(Android Metro风格的Launcher开发系列第二篇)说到Launcher主体框架用ViewPager来实现,这一篇博客咱们来说说每一个page的具体实现。

PagerAdapter:

Launcher主体ViewPager实现就引出了PagerAdapter,PagerAdapter是Android.support.v4包中的 类,它的子类有FragmentPagerAdapter, FragmentStatePagerAdapter,这两个adapter都是Fragment的适配器,这里因为没有用到Fragment所以这里不 讲,我只讲PagerAdapter。关于PageAapter的描述,Google官网原文是这样的:Base class providing the adapter to populate pages inside of a ViewPager.  You will most likely want to use a more specific implementation of this, such as FragmentPagerAdapter or FragmentStatePagerAdapter,大致就是说PagerAdapter是ViewPager提供的一个适配器,方便我们对 ViewPager的每一个View进行控制。我的PagerAdapter是这样实现的:

public class LauncherAdapter extends PagerAdapter {
private ArrayList mViews;

public LauncherAdapter(ArrayList views) {
mViews = views;
}

@Override
public void destroyItem(View arg0, int arg1, Object arg2) {
((ViewPager) arg0).removeView(mViews.get(arg1));
}

@Override
public void finishUpdate(View arg0) {
}

@Override
public int getCount() {
if (mViews != null) {
return mViews.size();
}
return 0;
}

public View getCurrentView(int currentID) {
return mViews.get(currentID);
}

@Override
public Object instantiateItem(View arg0, int arg1) {
((ViewPager) arg0).addView(mViews.get(arg1));
return mViews.get(arg1);
}

@Override
public boolean isViewFromObject(View arg0, Object arg1) {
return (arg0 == arg1);
}

@Override
public void restoreState(Parcelable arg0, ClassLoader arg1) {
}

@Override
public Parcelable saveState() {
return null;
}

}

PageViewItem:

PagerAdapter的getCurrentView方法返回的每一个view都是自定义View,为什么要自定义呢?因为在每一个图标获取焦点放大的时候会与旁边的图标有重叠部分,ViewPager每一页view都是一个FrameLayout,在绘制view的时候是按照一定的顺序绘制的,就会遇到焦点view放大后显示的效果是被旁边的view压了一部分,如果不改变view绘制顺序就不能避免这个问题。

如上图所示,图一显示效果就是焦点view放大,改变绘制顺序的实现效果。改变绘制顺序其实就是重写ViewGroup的getChildDrawingOrder(int childCount, int i)方法,每一次绘制时,最后返回focusview所在的viewgroup中的index就行了。

CellView:

如上图所示,每一个正方形的view我在这里叫做CellView,它也是一个自定义的view,自定义主要是为了实现:

1、获取焦点时放大和丢掉焦点时缩小效果,这里是应用了属性动画,ViewPropertyAnimator可以通过View的animate()方法获取的,具体动画实现如下:

mPropertyAnimator.scaleX((width + mScaleX) / width)
.scaleY((height + mScaleY) / height).setDuration(duration)
.setInterpolator(new DecelerateInterpolator())
.start();

2、在xml文件灵活配置一些CellView的属性,比如点击打开的应用,呈现的ICON获取地址,焦点x、y的放大值等,CellView对应的属性定义attrs.xml文件如下:

<!--?xml version="1.0" encoding="utf-8"?-->

&nbsp;

3、实现在用遥控器移动焦点时不会焦点错乱,在开发遥控器应用时一个很大的问题就是焦点在移动时焦点错乱,基本 上应用UI bug至少有一半时焦点bug,这个应用我为了防止焦点错乱定义了CellView的边界属性,上面的xml文件中isXXEdge就是,这样在焦点移动 到边界时可以进行Page之间的切换和其他处理,防止焦点在进入每一个page时出现错乱。

 

下面来看一下实现的具体效果:

 

 

总结:以上就是Metro风格Launcher实现,我用了三篇博客来讲解这个应用,所有效果的实现都是自己摸索的,应该还有更好的实现方法,大家可以多多交流提出自己的看法,也可以关注我的微信号coder_online,以上谢谢!

 

 

  第一时间获得博客更新提醒,以及更多技术信息分享,欢迎关注个人微信公众平台:程序员互动联盟(coder_online),扫一扫下方二维码或搜索微信号coder_online即可关注,我们可以在线交流。

                                                          

做站要先做人,总会得到回报的

mikel阅读(1172)

对于体验站来说,推广是摆在每个站长面前的一件头等大事儿,没有注册量就意味着没有任务转化率,也就没有收入,盈利模式就是赚联盟的任务差价,没有别的收入渠道,所以大家都很专注在推广这一块,自从2013年以来互联网的体验站如雨后春笋般茁壮成长起来,不单单是各个老站把握着市场,因为作为体验站来说的留存率很低,回头率基本上都是一时的,有高额的赏金任务就会有一批人来做,赚一把就走了。

所以说做体验站大部分都在拼转化率,也就是任务的转化率,往往做任务的就那么多人,来源也基本上固定在网赚论坛等等站点,推广不管是悬赏注册激励还是奖励都是吸引眼球的事儿,很多新站不被人重视,就是因为怕新站提现支付押款,很多人辛苦打码或者做任务赚了几块钱,你还拖着不支付给他,让人很心寒,还有就是推广你的站注册了几个人,结果提现不及时,伤了推广员的心,网站运营首先要建立基本的信任,不要拖欠提现的钱,大家都不容易草根何必为难草根儿呢!

运营网站最关键的一点就是要持之以恒,信任也不是一天建立起来的,我的站建了3年了,至今盈利不多,主要原因还是自己懒,懒得推广,但是坚持不押款及时支付,珍惜每个注册的用户和帮忙推广的人,每次提现都表达感谢,并建群加他们及时对网站的新功能和任务进行沟通反馈,努力做到让这些人成为忠实的用户,树立口碑,最近注册量上来了,通过网站统计多数是因为各位网赚站长的推荐给力,很多都是多次推广提现的老推广员的站了,真的非常感谢他们!做站要先做人,总会得到回报的!

 

没人认识的“大白”(●—●)

mikel阅读(1219)

清明小长假,带着女儿爬山逛夜市,看到有人在卖大白(●—●)玩偶,女儿看到了就不肯挪步了,看过《超能特工队》的女儿和我都非常喜欢,于是给她买了一个,女儿爱不释手,抱着继续逛街,一路上很多人看到她怀里的大白,有人认识,有人不认识。认识的总会怀着喜欢的喊一声:大白(●—●)!

路过地摊有些摊主看到女儿那么喜欢的样子,好奇的问这个玩偶是啥?女儿有点儿自豪的回答:大白!

那些没看过电影的人很不解这么一个看着样子挺怪的玩偶有什么可爱之处让她爱不释手。

女儿对这些不认识的人感到很奇怪,电影里那么可爱的大白,他们居然认为不可爱,还觉得样子怪怪的,我跟她解释说:这些人没看过电影,所以不认识大白很正常,也就不知道大白多可爱了。她觉得有道理的点点头。

不由得想到互联网企业的产品是不是很多时候也遭遇这种尴尬,小众人群的产品,小众里面的人爱不释手,大众不知所谓甚至不理解这些人为啥要用这么个产品,一个产品从诞生到成为大众品牌得到大众的认可看来要经历的过程不可谓不漫长,一部电影造就了一个角色,但是要想广而告之那就不是只能靠电影能够人尽皆知了。可见想要将产品深入人心有多难!很佩服可口可乐、小米那些公司的品牌塑造到人的心智里,一个名字就知道其价值。

54fd0c783dfae942d4000005

不积跬步无以至千里

mikel阅读(1289)

很多人都在找好的网络营销方法,各个听到有人发布新的营销方法,就蜂拥而至纷纷效仿,然后看不到效果就扭头去找其他的方法,其实这种人只能说是在想着一夜暴富做互联网,完全是赌徒心理,没有将互联网项目当成事业在经营,其实互联网上很多事儿需要路遥知马力的,根本不是那些骗子鼓吹的日赚多少等等。

今天看到有人分享的《巧借另类视频营销,自动获得大流量》的方法,看了下作者的确用心,深挖了视频营销的定位和主题选择,还有植入广告的技巧,但是重要的是作者说道的:

“这样短短的小视频,也就是三分钟,上传轻松简单,审核通过率高。普及开来,每天上传十个,坚持三个月,你可以上传900个视频。到时候,流量会超出你的想象。”

可见如果没有大毅力很难看到效果,这就是要互联网项目的操作的关键点要持之以恒,另外要勇于尝试,做才有机会,不做永远就是想象。

 

[转载]游标不包括正在修改的表,或该表不能通过此游标更新 - xiaoli_zhou2011的日志 - 网易博客

mikel阅读(1612)

[转载]游标不包括正在修改的表,或该表不能通过此游标更新 – xiaoli_zhou2011的日志 – 网易博客.

(asp)连接SQL2005时,SQL数据库名字不能改,改过以后就会报这样的错

错误一:
[Microsoft][ODBC SQL Server Driver][SQL Server]在 sys.servers 中找不到服务器 ‘anywolfs.com’。请验证指定的服务器名称是否正确。如果需要,请执行存储过程 sp_addlinkedserver 以将服务器添加到 sys.servers。

错误二:
[Microsoft][ODBC SQL Server Driver][SQL Server]未将服务器 ‘anywolfs.com’ 配置为用于 DATA ACCESS。

错误三:
[Microsoft][ODBC SQL Server Driver][SQL Server]此游标不包括正在修改的表,或该表不能通过此游标更新。

在asp中如果我使用rs.add和rs.update来添加数据就会报错误三,如果我使用insert into,就不会发生任何错误。在网上搜索半天,发现解决方法挺好用,方法如下:

解决:

如果是直接更换的服务器,那么主机名更换了,以前存在数据库的主机名也需要删除。进入SQL2005查询器
执行:

select @@servername
EXEC sp_dropserver @@servername
EXEC sp_addserver [new_name], local

第一行是查出老的实例名
第二行是删除老的实例名
第三行是 ‘new_name’ 是新的实例名,也就是当前计算机名,在重启一下sql2005服务,

执行完这一步,这时候如果asp会报错:

Microsoft OLE DB Provider for ODBC Drivers 错误 ‘80004005’
[Microsoft][ODBC SQL Server Driver][SQL Server]未将服务器 ‘anywolfs.com’ 配置为用于 DATA ACCESS。

需要设置模式,执行:
EXEC sp_serveroption ‘anywolfs.com’, ‘data access’, ‘true’

这时如果asp遇到:

[Microsoft][ODBC SQL Server Driver][SQL Server]此游标不包括正在修改的表,或该表不能通过此游标更新。
则需要再重启sql2005,

重 启后还是没有解决问题,那么可以使用sp_helpserver 查看有多少实例名,然后统统都给他删掉(sp_dropserver @@服务器名    来删除所有服务器),在执行 sp_addserver 等其他操作。或者重新运行一下上面的命令,重新做一遍,重启sql2005解决不了问题可以重启服务器看看。

我遇到的是错误3

先查询出来再全都删除,在创建

 

[转载]在sysservers 中找不到服务器 "名字 " 执行 sp_addlinkedserver 将该服务器添加到 “名字” - keilsi的专栏 - 博客频道 - CSDN.NET

mikel阅读(1183)

[转载]在sysservers 中找不到服务器 “名字 ” 执行 sp_addlinkedserver 将该服务器添加到 “名字” – keilsi的专栏 – 博客频道 – CSDN.NET.

Microsoft OLE DB Provider for SQL Server 错误 ‘80004005’Microsoft OLE DB Provider for SQL Server 错误 ‘80004005’

在 sysservers 中找不到服务器 ‘WebServer’。请执行 sp_addlinkedserver 将该服务器添加到 sysservers。

sql2005当用记录集删除时出现问题,增加修改记录都没问题
SQL服务器的问题,准确地说,是改了你的数据库的主机名导致SQL链接服务器没有随着更新,你可以新建一个查询,输入select * from master..sysservers,查询SQL链接服务器,看一下srvname是什么,与主机名相同吗?

所以,你得更新链接服务器,删除以前的那个链接服务器,或者把主机名改回来
登录到服务器 ,在查询窗口中执行如下两条命令 :
1,sp_dropserver ‘oldComputerName’
2,sp_addserver ‘newComputerName’,local
3重新启动服务就可以继续进行发布的创建了

[转载]Android 高手进阶之自定义View,自定义属性(带进度的圆形进度条) - Mobile Internet developer - 博客频道 - CSDN.NET

mikel阅读(994)

[转载]Android 高手进阶之自定义View,自定义属性(带进度的圆形进度条) – Mobile Internet developer – 博客频道 – CSDN.NET.

转载请注明地址:http://blog.csdn.net/xiaanming/article/details/10298163

很 多的时候,系统自带的View满足不了我们功能的需求,那么我们就需要自己来自定义一个能满足我们需求的View,自定义View我们需要先继承 View,添加类的构造方法,重写父类View的一些方法,例如onDraw,为了我们自定义的View在一个项目中能够重用,有时候我们需要自定义其属 性,举个很简单的例子,我在项目中的多个界面使用我自定义的View,每个界面该自定义View的颜色都不相同,这时候如果没有自定义属性,那我们是不是 需要构建不同颜色的View出来呢,这样子我们的代码就会显得很沉厄,所以这时候我们就需要自定义其属性来满足我们不同的需求,自定义属性呢,我们需要在 values下建立attrs.xml文件,在其中定义我们需要定义的属性,然后在自定义View中也要做相对应的修改,我们还是用一个小例子来看看自定 义View和自定义属性的使用

 

今天带大家来自己定义一个带进度的圆形进度条,我们还是先看一下效果吧

从上面可以看出,我们可以自定义圆环的颜色,圆环进度的颜色,是否显示进度的百分比,进度百分比的颜色,以及进度是实心还是空心等等,这样子是不是很多元化很方便呢?接下来我们就来教大家怎么来定义

 

1.在values下面新建一个attrs.xml,现在里面定义我们的属性,不同的属性对应不同的format,属性对应的format可以参考http://blog.csdn.net/pgalxx/article/details/6766677,介绍的还是比较详细,接下来我贴上我在自定义这个进度条所用到的属性

<!--?xml version="1.0" encoding="UTF-8"?-->















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);

上面的代码中,如roundColor = mTypedArray.getColor(R.styleable.RoundProgressBar_roundColor, Color.RED); getColor方法的第一个参数是我们在XML文件中定义的颜色,如果我们没有给我们自定义的View定义颜色,他就会使用第二个参数中的默认值,即Color.RED

3.为了方便大家理解,我将自定义View的全部代码贴出来,里面的代码我也有详细的注释

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,可直接在线程中更新进度
* @author xiaanming
*
*/
public class RoundProgressBar extends View {
/**
* 画笔对象的引用
*/
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 &amp;&amp; percent != 0 &amp;&amp; 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 &lt; 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 &lt; 0){ throw new IllegalArgumentException("progress not less than 0"); } if(progress &gt; max){
progress = max;
}
if(progress &lt;= 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.通过上面几步我们就实现了自定义View,和自定义View的属性,当然使用过程中还是有一点变化,我们必须在界面布局的最顶层加上

xmlns:Android_custom=”http://schemas.Android.com/apk/res/com.example.circlepregress”这个即命名空间,

红色部分是自定义属性的前缀,什么意思呢?对于Android系统控件我们定义其控件属性是用android:XXX=”XXXXXXX”,而我们自己定义的就用android_custom:XXX = “XXXXXX”
绿色部分则是我们的包的名字

通过上面这两步我们就能自己定义属性了,我贴出自定义View在XML中使用情况


今天就到此结束,如果大家有什么疑问,请留言,我会及时回复大家的

 

项目源码,点击下载

 

[转载]Android动画原理分析-android-爱编程

mikel阅读(960)

[转载]Android动画原理分析-android-爱编程.

最近在Android上做了一些动画效果,网上查了一些资料,有各种各样的使用方式,于是乘热打铁,想具体分析一下动画是如何实现的,Animation, Animator都有哪些区别等等。

首先说AnimationAndroid.view.animation.Animation)对象。

无论是用纯java代码构建Animation对象,还是通过xml文件定义Animation,其实最终的结果都是

Animation a = new AlphaAnimation();

Animation b = new ScaleAnimation();

Animation c = new RotateAnimation();

Animation d = new TranslateAnimation();

分别是透明度,缩放,旋转,位移四种动画效果。

而我们使用的时候,一般是用这样的形式:

View.startAnimation(a);

那么就来看看View中的startAnimation()方法。

1.View.startAnimation(Animation)

先是调用View.setAnimation(Animation)方法给自己设置一个Animation对象,这个对象是View类中的一个名为mCurrentAnimation的成员变量。

然后它调用invalidate()来重绘自己。

我想,既然setAnimation()了,那么它要用的时候,肯定要getAnimation(),找到这个方法在哪里调用就好了。于是通过搜索,在View.draw(Canvas, ViewGroup, long)方法中发现了它的调用,代码片段如下:

2.View.draw(Canvas, ViewGroup, long)

其中调用了View.drawAnimation()方法。

3.View.drawAnimation(ViewGroup, long, Animation, boolean)

代码片段如下:

其中调用了Animation.getTransformation()方法。

4.Animation.getTransformation(long, Transformation, float)

该方法直接调用了两个参数Animation.getTransformation()方法。

5.Animation.getTransformation(long, Transformation)

该方法先将参数currentTime处理成一个float表示当前动画进度,比如说,一个2000ms的动画,已经执行了1000ms了,那么进度就是0.5或者说50%。

然后将进度值传入插值器(Interpolator)得到新的进度值,前者是均匀的,随着时间是一个直线的线性关系,而通过插值器计算后得到的是一个曲线的关系。

然后将新的进度值和Transformation对象传入applyTranformation()方法中。

6.Animation.applyTransformation(float, Transformation)

Animation的applyTransformation()方法是空实现,具体实现它的是Animation的四个子类,而该方法正是真正的处理动画变化的过程。分别看下四个子类的applyTransformation()的实现。

ScaleAnimation

AlphaAnimation

RotateAnimation

TranslateAnimation

可见applyTransformation()方法就是动画具体的实现,系统会以一个比较高的频率来调用这个方法,一般情况下60FPS,是一个非常流畅的画面了,也就是16ms,为了验证这一点,我在applyTransformation方法中加入计算时间间隔并打印的代码进行验证,代码如下:

最终得到的log如下图所示:

右侧是“手动”计算出来的时间差,有一定的波动,但大致上是16-17ms的样子,左侧是日志打印的时间,时间非常规则的相差20ms。

于是,根据以上的结果,可以得出以下内容:

1.首先证明了一点,Animation.applyTransformation()方法,是动画具体的调用方法,我们可以覆写这个方法,快速的制作自己的动画。

2.另一点,为什么是16ms左右调用这个方法呢?是谁来控制这个频率的?

对于以上的疑问,我有两个猜测:

1.系统自己以postDelayed(this, 16)的形式调用的这个方法。具体的写法,请参考《使用线程实现视图平滑滚动》

2.系统一个死循环疯狂的调用,运行一系列方法走到这个位置的间隔刚好是16ms左右,如果主线程卡了,这个间隔就变长了。

为了找到答案,我在Stack Overflow上发帖问了下,然后得到一个情报,那就是让我去看看Choreographer(Android.view.Choreographer)类。

1.Choreographer的构造方法

看了下Choreographer类的构造方法,是private的,不允许new外部类new,于是又发现了它有一个静态的getInstance()方法,那么,我需要找到getInstance()方法被谁调用了,就可以知道Choreographer对象在什么地方被使用。一查,发现Choreographer.getInstance()在ViewRootImpl的构造方法中被调用。以下代码是ViewRootImpl.ViewRootImpl(Context, Display)的片段。

OK,找到了ViewRootImpl中拥有一个mChoreographer对象,接下来,我需要去找,它如何被使用了,调用了它的哪些方法。于是发现如下代码:

scheduleTraversals()方法中,发现了这个对象的使用。

2.Choreographer.postCallback(int, Runnable, Object)

该方法辗转调用了两个内部方法,最终是调用了Choreographer.postCallbackDelayedInternal()方法。

3.Choreographer.postCallbackDelayedInternal(int, Object, Object, long)

这个方法中,

1.首先拿到当前的时间。

这里参数中有一个delay,它的值可以具体查看一下,你会发现它就是一个静态常量,定义在Choreographer类中,它的值是10ms。也就是说,理想情况下,所有的时间都是以100FPS来运行的。

2.将要执行的内容加入到一个mCallbackQueues中。

3.然后执行scheduleFrameLocked()或者发送一个Message。

接着我们看Choreographer.scheduleFrameLocked(long)方法

4.Choreographer.scheduleFrameLocked(long)

if判断进去的部分是是否使用垂直同步,暂时不考虑。

else进去的部分,还是将消息发送到mHandler对象中。那我们就直接来看mHandler对象就好了

5.Choreographer.FrameHandler

mHandler实例的类型是FrameHandler,它的定义就在Choreographer类中,代码如下:

它的处理方法中有三个分支,但最终都会调用这个doFrame()方法。

6.Choreographer.doFrame()

doFrame()方法巴拉巴拉一大段,但在下面有非常工整的一段代码,一下就吸引了我的眼球。

它调用了三次doCallbacks()方法,暂且不说这个方法是干什么的,但从它的第一个参数可以看到分别是输入(INPUT),动画(ANIMATION),遍历(TRAVERSAL)

于是,我先是看了下这三个常量的意义。下图所示:

显然,注释是说:输入事件最先处理,然后处理动画,最后才处理view的布局和绘制。接下来我们看看Choreographer.doCallbacks()里面做了什么。

7.Choreographer.doCallbacks(int, long)

这个方法的操作非常统一,有三种不同类型的操作(输入,动画,遍历),但在这里却看不见这些具体事件的痕迹,这里我们不得不分析一下mCallbackQueues这个成员变量了。

mCallbackQueues是一个CallbackQueue对象数组。而它的下标,其意义并不是指元素1,元素2,元素3……而是指类型,请看上面doCallbacks()的代码,参数callbackType传给了mCallbackQueues[callbackType]中,而callbackType是什么呢?

其实就是前面说到的三个常量,CALLBACK_INPUT, CALLBACK_ANIMATION, CALLBACK_TRAVERVAL

那么只需要根据不同的callbackType,就可以从这个数组里面取出不同类型的CallbackQueue对象来。

那么CallbackQueue又是什么呢?

CallbackQueueChoreographer的一个内部类,其中我认为有两个很重要的方法,分别是:extractDueCallbacksLocked(long)addCallbackLocked(long, Object, Object)

先说addCallbackLocked(long, Object, Object)

1.CallbackQueue.addCallbackLocked(long, Object, Object)

首先它通过一个内部方法构建了一个CallbackRecord对象,然后后面的if判断和while循环,大致上是将参数中的对象链接在CallbackRecord的尾部。其实CallbackRecord就是一个链表结构的对象。

2.CallbackQueue.extractDueCallbacksLocked(long)

这个方法是根据当前的时间,选出执行链表中与该时间最近的一个操作来处理,实际上,我们可以通俗的理解为“跳帧”。

想象一下,如果主线程运行的非常快速,非常流畅,每一步都能在10ms内准时运行到,那么我们的执行链表中的元素始终只有一个。

如果主线程中做了耗时操作,那么各种事件一直在往各自的链表中添加,但是当主线程有空来执行的时候,发现链表已经那么多积累的过期的事件了,那么就直接选择最后一个来执行,那么界面上看起来,就是卡顿了一下。

到这里为止,我们得出以下结论:

1.控制外部输入事件处理,动画执行,UI变化都是在同一个类中做的处理,即是Choreographer,其中它规定的了理想的运行间隔为10ms,因为各种操作需要花费一定的时间,所以外部执行的间隔统计出来是大约16ms。

2.在Choreographer对象中有三条链表,分别保存着待处理的输入事件,待处理的动画事件,待处理的遍历事件。

3.每次执行的时候,Choreographer会根据当前的时间,只处理事件链表中最后一个事件,当有耗时操作在主线程时,事件不能及时执行,就会出现所谓的“跳帧”,“卡顿”现象。

4.Choreographer的共有方法postCallback(callbackType, Object)是往事件链表中放事件的方法。而doFrame()是消耗这些事件的方法。

事到如今,已经探究出不少有用的细节。这里,又给自己提出一个问题,根据以上的事实,那么,只需要找到哪些东西再往这三条链表中放事件呢?

于是进一步探究一下。我们只需要找到postCallback()被哪些方法调用了即可。

于是请点击这里,通过grepcode列举了调用postCallback()的方法。

搞明白了Choreographer的工作原理,再去看ObjectAnimator,ValueAnimator的实现,就非常的轻松了。

ObjectAnimator.start()方法实际上是辗转几次调用了ValueAnimator的start()方法,ValueAnimator.start()又调用了一个临时变量animationHandler.start()。

animationHandler实际上是一个Runnable,其中start()方法调用了scheduleAnimation()。

而这个方法:

调用了postCallback()方法。

将this(Runnable)post之后,实际上肯定就是要执行Runnable.run()方法

run()方法中又调用了doAnimationFrame()方法。这个方法具体的实现了动画的某一帧的过程,然后再次调用了scheduleAnimation()方法。

就相当于postDelayed(this, 16)这种方式了。

到这里为止,对Animation原理的分析就到此结束了,本来只想分析下Animation的实现过程,没想到顺带研究了一下Choreographer的工作原理,今天收获还是不少。

其实还有好多疑问,技术学习也一天急不得,靠的是每日慢慢的积累,相信总有一天,各种疑惑都会迎刃而解。

[转载]解决IllegalStateException: Can not perform this action after onSaveInstanceState - 第一菜鸟XMU - 博客频道 - CSDN.NET

mikel阅读(1074)

[转载]解决IllegalStateException: Can not perform this action after onSaveInstanceState – 第一菜鸟XMU – 博客频道 – CSDN.NET.

今天使用Fragment的时候,出现了这个错误 IllegalStateException: Can not perform this action after onSaveInstanceState:

  1. E/AndroidRuntime(12747): Caused by: java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
  2.     at Android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1314)
  3.     at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1325)

 

是在使用FragmentTransition的 commit方法添加一个Fragment的时候出现的,后来在官网找到了相关的

说明:http://developer.android.com/reference/android/app/FragmentTransaction.html#commitAllowingStateLoss()

public abstract int commitAllowingStateLoss ()                                    Added in API level 11

Like commit() but allows the commit to be executed after an activity’s state is saved. This is dangerous

because the commit can be lost if the activity needs to later be restored from its state, so this should

only be used for cases where it is okay for the UI state to change unexpectedly on the user.

大致意思是说我使用的 commit方法是在Activity的onSaveInstanceState()之后调用的,这样会出错,因为onSaveInstanceState

方法是在该Activity即将被销毁前调用,来保存Activity数据的,如果在保存玩状态后再给它添加Fragment就会出错。解决办法就

是把commit()方法替换成 commitAllowingStateLoss()就行了,其效果是一样的。