导航抽屉上的动画在所有设备上都不流畅
Animation on Navigation Drawer not smooth on all devices
我的应用程序中有一个带以下控件的导航抽屉:
1) ImageView 和 ProgressBar
2)ImageView和ProgressBar下面的ListView。
这个列表中的每个元素在抽屉打开时都是动画的,我正在为 Listview 元素设置动画,如下所示:
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
LayoutInflater mInflater = (LayoutInflater)context.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
convertView = mInflater.inflate(R.layout.drawer_list_item, null);
}
ImageView imgIcon = (ImageView) convertView.findViewById(R.id.icon);
TextView txtTitle = (TextView) convertView.findViewById(R.id.title);
TextView txtCount = (TextView) convertView.findViewById(R.id.counter);
AnimatorSet sunSet = (AnimatorSet) AnimatorInflater.loadAnimator(MainActivity.con, R.animator.sun_swing);
AnimatorSet wheelSet = (AnimatorSet) AnimatorInflater.loadAnimator(MainActivity.con, R.animator.wheel_spin);
//set the view as target
sunSet.setTarget(imgIcon);
//start the animation
sunSet.start();
wheelSet.setTarget(imgIcon);
//start the animation
wheelSet.start();
ObjectAnimator textAnim2 = ObjectAnimator.ofFloat(txtTitle, "x",convertView.getWidth()-(txtTitle.getWidth()/2), (convertView.getWidth()/2)-70);
textAnim2.setDuration(1000);
textAnim2.setRepeatCount(0);
textAnim2.start();
txtTitle.setTypeface(tf);
txtTitle.setTextSize(18);
imgIcon.setImageResource(navDrawerItems.get(position).getIcon());
txtTitle.setText(navDrawerItems.get(position).getTitle());
//convertView.startAnimation(getMaximAnim());
if(navDrawerItems.get(position).getCounterVisibility()){
txtCount.setText(navDrawerItems.get(position).getCount());
}else{
// hide the counter view
txtCount.setVisibility(View.GONE);
}
return convertView;
}
这是我的wheel_spin:
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:ordering="sequentially" >
<objectAnimator
android:duration="1000"
android:propertyName="rotation"
android:repeatCount="0"
android:repeatMode="reverse"
android:valueFrom="100"
android:valueTo="0"
android:valueType="floatType" />
</set>
这是我的 sun_swing:
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:ordering="sequentially" >
<objectAnimator
android:duration="1000"
android:propertyName="x"
android:repeatCount="0"
android:valueFrom="230"
android:valueTo="20"
android:valueType="floatType" />
</set>
清单中:
<application
android:allowBackup="true"
android:hardwareAccelerated="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/CustomActionBarTheme" >
在 onDrawerOpen
runOnUiThread(run);
我的 运行 是:
final Runnable run = new Runnable(){
public void run(){
//reload content
adapter.notifyDataSetChanged();
mDrawerList.invalidateViews();
mDrawerList.refreshDrawableState();
}
};
我正在执行上述操作,以便在打开导航抽屉时始终获得动画效果。我发现我的动画在大多数手机上都能正常工作,但是 4.0 和 4.4 之间的一些手机 运行 使这个动画带有抖动。可能的原因是什么以及如何 avoid/overcome 相同?
这是导航抽屉打开和关闭调用之间的轨迹:
编辑 2:我已经从下面的 SGal 的回答中实现了很多,我更新的代码如下所示:
public class NavDrawerListAdapter extends BaseAdapter {
private Context context;
private ArrayList<NavDrawerItem> navDrawerItems;
String fontPath = "fonts/HelveticaNeue-Light.otf";
Typeface tf;
ObjectAnimator textAnim2;
ObjectAnimator spin;
ObjectAnimator swing;
public NavDrawerListAdapter(Context context, ArrayList<NavDrawerItem> navDrawerItems){
this.context = context;
this.navDrawerItems = navDrawerItems;
tf = Typeface.createFromAsset(context.getAssets(), fontPath);
}
@Override
public int getCount() {
return navDrawerItems.size();
}
@Override
public Object getItem(int position) {
return navDrawerItems.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
System.out.println("Position: "+position);
ViewHolder viewHolder = new ViewHolder();
if (convertView == null) {
LayoutInflater mInflater = (LayoutInflater)context.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
convertView = mInflater.inflate(R.layout.drawer_list_item, null);
viewHolder.imgIcon = (ImageView) convertView.findViewById(R.id.icon);
viewHolder.txtTitle = (TextView) convertView.findViewById(R.id.title);
viewHolder.txtCount = (TextView) convertView.findViewById(R.id.counter);
convertView.setTag(viewHolder);
}
viewHolder = (ViewHolder) convertView.getTag();
// if(position == 0){
textAnim2 = ObjectAnimator.ofFloat(viewHolder.txtTitle, "x",convertView.getWidth()-(viewHolder.txtTitle.getWidth()/2), ((viewHolder.imgIcon.getWidth())+30));
textAnim2.setDuration(1000);
textAnim2.setRepeatCount(0);
spin = ObjectAnimator.ofFloat(viewHolder.imgIcon, "rotation", 70f , 0f);
spin.setDuration(1000);
spin.setRepeatCount(0);
swing = ObjectAnimator.ofFloat(viewHolder.imgIcon, "x",convertView.getWidth()-(viewHolder.txtTitle.getWidth()+80), 30);
swing.setDuration(1000);
swing.setRepeatCount(0);
/* }else if(position == 1){
textAnim2 = ObjectAnimator.ofFloat(viewHolder.txtTitle, "x",convertView.getWidth()-(viewHolder.txtTitle.getWidth()/2), ((viewHolder.imgIcon.getWidth())+30));
textAnim2.setDuration(1000);
textAnim2.setRepeatCount(0);
spin = ObjectAnimator.ofFloat(viewHolder.imgIcon, "rotation", 70f , 0f);
spin.setDuration(1000);
spin.setRepeatCount(0);
swing = ObjectAnimator.ofFloat(viewHolder.imgIcon, "x",convertView.getWidth()-(viewHolder.txtTitle.getWidth()+120), 30);
swing.setDuration(1000);
swing.setRepeatCount(0);
}else if(position == 2){
textAnim2 = ObjectAnimator.ofFloat(viewHolder.txtTitle, "x",convertView.getWidth()-(viewHolder.txtTitle.getWidth()/2), ((viewHolder.imgIcon.getWidth())+30));
textAnim2.setDuration(1000);
textAnim2.setRepeatCount(0);
spin = ObjectAnimator.ofFloat(viewHolder.imgIcon, "rotation", 70f , 0f);
spin.setDuration(1000);
spin.setRepeatCount(0);
swing = ObjectAnimator.ofFloat(viewHolder.imgIcon, "x",convertView.getWidth()-(viewHolder.txtTitle.getWidth()+140), 30);
swing.setDuration(1000);
swing.setRepeatCount(0);
} */
swing.start();
textAnim2.start();
spin.start();
viewHolder.txtTitle.setTypeface(tf);
viewHolder.txtTitle.setTextSize(18);
viewHolder.imgIcon.setImageResource(navDrawerItems.get(position).getIcon());
viewHolder.txtTitle.setText(navDrawerItems.get(position).getTitle());
if(navDrawerItems.get(position).getCounterVisibility()){
viewHolder.txtCount.setText(navDrawerItems.get(position).getCount());
}else{
viewHolder.txtCount.setVisibility(View.GONE);
}
return convertView;
}
static class ViewHolder {
ImageView imgIcon;
TextView txtTitle;
TextView txtCount;
}
首先,findViewById 很昂贵,这就是为什么使用 ViewHolder 模式总是一个好主意。
其次,您在每次 getView() 调用时执行 2 xml 动画 inflation。 XML inflations 是更昂贵的操作。
考虑像这样重写您的 getView 方法。将 Animator inflation 移动到适配器构造函数并在 getView() 中重复使用它们还添加了一个 viewHolder 以提高列表滚动性能(如果你有一个长菜单列表则很有用):
private Activity ctx;
private AnimatorSet sunSet;
private AnimatorSet wheelSet;
public MyAdapter(Activity ctx){
this.ctx = ctx;
this.sunSet = (AnimatorSet) AnimatorInflater.loadAnimator(ctx, R.animator.sun_swing);
this.wheelSet = (AnimatorSet) AnimatorInflater.loadAnimator(ctx, R.animator.wheel_spin);
}
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder = new ViewHolder();
if (convertView == null) {
convertView = ctx.getLayoutInflater().inflate(R.layout.drawer_list_item, parent, false);
viewHolder.imgIcon = (ImageView) convertView.findViewById(R.id.icon);
viewHolder.txtTitle = (TextView) convertView.findViewById(R.id.title);
viewHolder.txtCount = (TextView) convertView.findViewById(R.id.counter);
convertView.setTag(viewHolder);
}
viewHolder = (ViewHolderItem) convertView.getTag();
AnimatorSet sunSetClone = sunSet.clone();
sunSetClone.setTarget(viewHolder.imgIcon);
sunSetClone.start();
AnimatorSet wheelSetClone = wheelSet.clone();
wheelSetClone.setTarget(viewHolder.imgIcon);
wheelSetClone.start();
ObjectAnimator textAnim2 = ObjectAnimator.ofFloat(viewHolder.txtTitle, "x",convertView.getWidth()-(viewHolder.txtTitle.getWidth()/2), (convertView.getWidth()/2)-70);
textAnim2.setDuration(1000);
textAnim2.setRepeatCount(0);
textAnim2.start();
viewHolder.txtTitle.setTypeface(tf);
viewHolder.txtTitle.setTextSize(18);
viewHolder.imgIcon.setImageResource(navDrawerItems.get(position).getIcon());
viewHolder.txtTitle.setText(navDrawerItems.get(position).getTitle());
if(navDrawerItems.get(position).getCounterVisibility()){
viewHolder.txtCount.setText(navDrawerItems.get(position).getCount());
}else{
viewHolder.txtCount.setVisibility(View.GONE);
}
return convertView;
}
static class ViewHolder {
ImageView imgIcon;
TextView txtTitle;
TextView txtCount;
}
您可以添加的另一个性能改进是 imageView.setImageResource(navDrawerItems.get(position).getIcon());
正如 Android documentation 所述:
setImageResource: This does Bitmap reading and decoding on the UI
thread, which can cause a latency hiccup. If that's a concern,
consider using setImageDrawable(android.graphics.drawable.Drawable) or
setImageBitmap(android.graphics.Bitmap) and BitmapFactory instead.
我个人喜欢使用 AsyncDrawable
编辑:刚刚意识到它只会为一个列表项设置动画,因为我们总是覆盖目标视图。幸运的是 AnimatorSet 将 clone() 实现为 "deep copy",即使你克隆了一个 运行 动画,所有值都会重置,所以我更新了我的代码而不是:
sunSet.reset();
sunSet.setTarget(viewHolder.imgIcon);
sunSet.start();
wheelSet.reset();
wheelSet.setTarget(viewHolder.imgIcon);
wheelSet.start();
变成这样:
AnimatorSet sunSetClone = sunSet.clone();
sunSetClone.setTarget(viewHolder.imgIcon);
sunSetClone.start();
AnimatorSet wheelSetClone = wheelSet.clone();
wheelSetClone.setTarget(viewHolder.imgIcon);
wheelSetClone.start();
克隆仍然比 XML inflation 快得多。
我的应用程序中有一个带以下控件的导航抽屉:
1) ImageView 和 ProgressBar 2)ImageView和ProgressBar下面的ListView。
这个列表中的每个元素在抽屉打开时都是动画的,我正在为 Listview 元素设置动画,如下所示:
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
LayoutInflater mInflater = (LayoutInflater)context.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
convertView = mInflater.inflate(R.layout.drawer_list_item, null);
}
ImageView imgIcon = (ImageView) convertView.findViewById(R.id.icon);
TextView txtTitle = (TextView) convertView.findViewById(R.id.title);
TextView txtCount = (TextView) convertView.findViewById(R.id.counter);
AnimatorSet sunSet = (AnimatorSet) AnimatorInflater.loadAnimator(MainActivity.con, R.animator.sun_swing);
AnimatorSet wheelSet = (AnimatorSet) AnimatorInflater.loadAnimator(MainActivity.con, R.animator.wheel_spin);
//set the view as target
sunSet.setTarget(imgIcon);
//start the animation
sunSet.start();
wheelSet.setTarget(imgIcon);
//start the animation
wheelSet.start();
ObjectAnimator textAnim2 = ObjectAnimator.ofFloat(txtTitle, "x",convertView.getWidth()-(txtTitle.getWidth()/2), (convertView.getWidth()/2)-70);
textAnim2.setDuration(1000);
textAnim2.setRepeatCount(0);
textAnim2.start();
txtTitle.setTypeface(tf);
txtTitle.setTextSize(18);
imgIcon.setImageResource(navDrawerItems.get(position).getIcon());
txtTitle.setText(navDrawerItems.get(position).getTitle());
//convertView.startAnimation(getMaximAnim());
if(navDrawerItems.get(position).getCounterVisibility()){
txtCount.setText(navDrawerItems.get(position).getCount());
}else{
// hide the counter view
txtCount.setVisibility(View.GONE);
}
return convertView;
}
这是我的wheel_spin:
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:ordering="sequentially" >
<objectAnimator
android:duration="1000"
android:propertyName="rotation"
android:repeatCount="0"
android:repeatMode="reverse"
android:valueFrom="100"
android:valueTo="0"
android:valueType="floatType" />
</set>
这是我的 sun_swing:
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:ordering="sequentially" >
<objectAnimator
android:duration="1000"
android:propertyName="x"
android:repeatCount="0"
android:valueFrom="230"
android:valueTo="20"
android:valueType="floatType" />
</set>
清单中:
<application
android:allowBackup="true"
android:hardwareAccelerated="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/CustomActionBarTheme" >
在 onDrawerOpen
runOnUiThread(run);
我的 运行 是:
final Runnable run = new Runnable(){
public void run(){
//reload content
adapter.notifyDataSetChanged();
mDrawerList.invalidateViews();
mDrawerList.refreshDrawableState();
}
};
我正在执行上述操作,以便在打开导航抽屉时始终获得动画效果。我发现我的动画在大多数手机上都能正常工作,但是 4.0 和 4.4 之间的一些手机 运行 使这个动画带有抖动。可能的原因是什么以及如何 avoid/overcome 相同?
这是导航抽屉打开和关闭调用之间的轨迹:
编辑 2:我已经从下面的 SGal 的回答中实现了很多,我更新的代码如下所示:
public class NavDrawerListAdapter extends BaseAdapter {
private Context context;
private ArrayList<NavDrawerItem> navDrawerItems;
String fontPath = "fonts/HelveticaNeue-Light.otf";
Typeface tf;
ObjectAnimator textAnim2;
ObjectAnimator spin;
ObjectAnimator swing;
public NavDrawerListAdapter(Context context, ArrayList<NavDrawerItem> navDrawerItems){
this.context = context;
this.navDrawerItems = navDrawerItems;
tf = Typeface.createFromAsset(context.getAssets(), fontPath);
}
@Override
public int getCount() {
return navDrawerItems.size();
}
@Override
public Object getItem(int position) {
return navDrawerItems.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
System.out.println("Position: "+position);
ViewHolder viewHolder = new ViewHolder();
if (convertView == null) {
LayoutInflater mInflater = (LayoutInflater)context.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
convertView = mInflater.inflate(R.layout.drawer_list_item, null);
viewHolder.imgIcon = (ImageView) convertView.findViewById(R.id.icon);
viewHolder.txtTitle = (TextView) convertView.findViewById(R.id.title);
viewHolder.txtCount = (TextView) convertView.findViewById(R.id.counter);
convertView.setTag(viewHolder);
}
viewHolder = (ViewHolder) convertView.getTag();
// if(position == 0){
textAnim2 = ObjectAnimator.ofFloat(viewHolder.txtTitle, "x",convertView.getWidth()-(viewHolder.txtTitle.getWidth()/2), ((viewHolder.imgIcon.getWidth())+30));
textAnim2.setDuration(1000);
textAnim2.setRepeatCount(0);
spin = ObjectAnimator.ofFloat(viewHolder.imgIcon, "rotation", 70f , 0f);
spin.setDuration(1000);
spin.setRepeatCount(0);
swing = ObjectAnimator.ofFloat(viewHolder.imgIcon, "x",convertView.getWidth()-(viewHolder.txtTitle.getWidth()+80), 30);
swing.setDuration(1000);
swing.setRepeatCount(0);
/* }else if(position == 1){
textAnim2 = ObjectAnimator.ofFloat(viewHolder.txtTitle, "x",convertView.getWidth()-(viewHolder.txtTitle.getWidth()/2), ((viewHolder.imgIcon.getWidth())+30));
textAnim2.setDuration(1000);
textAnim2.setRepeatCount(0);
spin = ObjectAnimator.ofFloat(viewHolder.imgIcon, "rotation", 70f , 0f);
spin.setDuration(1000);
spin.setRepeatCount(0);
swing = ObjectAnimator.ofFloat(viewHolder.imgIcon, "x",convertView.getWidth()-(viewHolder.txtTitle.getWidth()+120), 30);
swing.setDuration(1000);
swing.setRepeatCount(0);
}else if(position == 2){
textAnim2 = ObjectAnimator.ofFloat(viewHolder.txtTitle, "x",convertView.getWidth()-(viewHolder.txtTitle.getWidth()/2), ((viewHolder.imgIcon.getWidth())+30));
textAnim2.setDuration(1000);
textAnim2.setRepeatCount(0);
spin = ObjectAnimator.ofFloat(viewHolder.imgIcon, "rotation", 70f , 0f);
spin.setDuration(1000);
spin.setRepeatCount(0);
swing = ObjectAnimator.ofFloat(viewHolder.imgIcon, "x",convertView.getWidth()-(viewHolder.txtTitle.getWidth()+140), 30);
swing.setDuration(1000);
swing.setRepeatCount(0);
} */
swing.start();
textAnim2.start();
spin.start();
viewHolder.txtTitle.setTypeface(tf);
viewHolder.txtTitle.setTextSize(18);
viewHolder.imgIcon.setImageResource(navDrawerItems.get(position).getIcon());
viewHolder.txtTitle.setText(navDrawerItems.get(position).getTitle());
if(navDrawerItems.get(position).getCounterVisibility()){
viewHolder.txtCount.setText(navDrawerItems.get(position).getCount());
}else{
viewHolder.txtCount.setVisibility(View.GONE);
}
return convertView;
}
static class ViewHolder {
ImageView imgIcon;
TextView txtTitle;
TextView txtCount;
}
首先,findViewById 很昂贵,这就是为什么使用 ViewHolder 模式总是一个好主意。
其次,您在每次 getView() 调用时执行 2 xml 动画 inflation。 XML inflations 是更昂贵的操作。
考虑像这样重写您的 getView 方法。将 Animator inflation 移动到适配器构造函数并在 getView() 中重复使用它们还添加了一个 viewHolder 以提高列表滚动性能(如果你有一个长菜单列表则很有用):
private Activity ctx;
private AnimatorSet sunSet;
private AnimatorSet wheelSet;
public MyAdapter(Activity ctx){
this.ctx = ctx;
this.sunSet = (AnimatorSet) AnimatorInflater.loadAnimator(ctx, R.animator.sun_swing);
this.wheelSet = (AnimatorSet) AnimatorInflater.loadAnimator(ctx, R.animator.wheel_spin);
}
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder = new ViewHolder();
if (convertView == null) {
convertView = ctx.getLayoutInflater().inflate(R.layout.drawer_list_item, parent, false);
viewHolder.imgIcon = (ImageView) convertView.findViewById(R.id.icon);
viewHolder.txtTitle = (TextView) convertView.findViewById(R.id.title);
viewHolder.txtCount = (TextView) convertView.findViewById(R.id.counter);
convertView.setTag(viewHolder);
}
viewHolder = (ViewHolderItem) convertView.getTag();
AnimatorSet sunSetClone = sunSet.clone();
sunSetClone.setTarget(viewHolder.imgIcon);
sunSetClone.start();
AnimatorSet wheelSetClone = wheelSet.clone();
wheelSetClone.setTarget(viewHolder.imgIcon);
wheelSetClone.start();
ObjectAnimator textAnim2 = ObjectAnimator.ofFloat(viewHolder.txtTitle, "x",convertView.getWidth()-(viewHolder.txtTitle.getWidth()/2), (convertView.getWidth()/2)-70);
textAnim2.setDuration(1000);
textAnim2.setRepeatCount(0);
textAnim2.start();
viewHolder.txtTitle.setTypeface(tf);
viewHolder.txtTitle.setTextSize(18);
viewHolder.imgIcon.setImageResource(navDrawerItems.get(position).getIcon());
viewHolder.txtTitle.setText(navDrawerItems.get(position).getTitle());
if(navDrawerItems.get(position).getCounterVisibility()){
viewHolder.txtCount.setText(navDrawerItems.get(position).getCount());
}else{
viewHolder.txtCount.setVisibility(View.GONE);
}
return convertView;
}
static class ViewHolder {
ImageView imgIcon;
TextView txtTitle;
TextView txtCount;
}
您可以添加的另一个性能改进是 imageView.setImageResource(navDrawerItems.get(position).getIcon());
正如 Android documentation 所述:
setImageResource: This does Bitmap reading and decoding on the UI thread, which can cause a latency hiccup. If that's a concern, consider using setImageDrawable(android.graphics.drawable.Drawable) or setImageBitmap(android.graphics.Bitmap) and BitmapFactory instead.
我个人喜欢使用 AsyncDrawable
编辑:刚刚意识到它只会为一个列表项设置动画,因为我们总是覆盖目标视图。幸运的是 AnimatorSet 将 clone() 实现为 "deep copy",即使你克隆了一个 运行 动画,所有值都会重置,所以我更新了我的代码而不是:
sunSet.reset();
sunSet.setTarget(viewHolder.imgIcon);
sunSet.start();
wheelSet.reset();
wheelSet.setTarget(viewHolder.imgIcon);
wheelSet.start();
变成这样:
AnimatorSet sunSetClone = sunSet.clone();
sunSetClone.setTarget(viewHolder.imgIcon);
sunSetClone.start();
AnimatorSet wheelSetClone = wheelSet.clone();
wheelSetClone.setTarget(viewHolder.imgIcon);
wheelSetClone.start();
克隆仍然比 XML inflation 快得多。