常常在开发过程中使用自定义的View,而自定义的View的图形往往是onDraw里面实现的,这样就可能因为在父容器里面而因为父容器稍微的变更,就会重绘,重绘是需要很多内存消耗的,而且如果父容器有背景色,那么onDraw所画的一切图形色彩都是再父容器的基础上进行,从而导致某一个像素点上,进行了多次渲染,这是需要内存消耗的.专家的建议是:
我们可以先按照通常的方式把View上的元素按照从后到前的方式绘制出来,但是不直接显示到屏幕上,而是使用GPU预处理之后,再又GPU渲染到屏幕上,GPU可以对界面上的原始数据直接做旋转,设置透明度等等操作。使用GPU进行渲染,虽然第一次操作相比起直接绘制到屏幕上更加耗时,可是一旦原始纹理数据生成之后,接下去的操作就比较省时省力。
个人觉得GPU就像一个相机,将View的图形在第一次渲染的时候拍下来,生成一个底片,下次再视图更新的时候,要显示自定义的View,GPU就把底片冲洗一下,贴出来,就不需要再去重新绘制了,这样就节省了很多内存消耗:
具体操作事例如下:
<1> : 新建一个Android工程:
<2> : 具体程序如下:
DurianMainActivity.java
package org.durian.duriangpuview; import android.graphics.Color; import android.os.Bundle; import android.support.v7.app.ActionBarActivity; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.Button; import android.widget.ListView; import java.util.List; public class DurianMainActivity extends ActionBarActivity { private ListView listView; private Button button; private DurianBaseAdapter mDurianBaseAdapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.durian_main); listView=(ListView)findViewById(android.R.id.list); listView.setBackgroundColor(Color.GRAY); mDurianBaseAdapter=new DurianBaseAdapter(this); listView.setAdapter(mDurianBaseAdapter); button=(Button)findViewById(R.id.button); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mDurianBaseAdapter.notifyDataSetChanged(); } }); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.menu_durian_main, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle action bar item clicks here. The action bar will // automatically handle clicks on the Home/Up button, so long // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); //noinspection SimplifiableIfStatement if (id == R.id.action_settings) { return true; } return super.onOptionsItemSelected(item); } }
DurianBaseAdapter.java
package org.durian.duriangpuview; import android.animation.Animator; import android.animation.ObjectAnimator; import android.content.Context; import android.util.Log; import android.view.ContextMenu; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import org.durian.duriangpuview.view.DurianGPUAlphaView; /** * Project name : DurianGPUView * Created by zhibao.liu on 2016/1/15. * Time : 11:58 * Email [email protected] * Action : durian */ public class DurianBaseAdapter extends BaseAdapter implements ObjectAnimator.AnimatorListener { private final static String TAG="DurianBaseAdapter"; private Context mContext; private LayoutInflater inflater; private ObjectAnimator objectAnimator=new ObjectAnimator(); public DurianBaseAdapter(Context context){ mContext=context; inflater=LayoutInflater.from(context); } @Override public int getCount() { return 150; } @Override public Object getItem(int position) { return null; } @Override public long getItemId(int position) { return 0; } @Override public View getView(int position, View convertView, ViewGroup parent) { Log.i(TAG,"*** getView position : "+position); if(convertView==null){ Log.i(TAG,"getView position : "+position); convertView=inflater.inflate(R.layout.list_view,null); viewHolder.gpuAlphaView=(DurianGPUAlphaView)convertView.findViewById(R.id.durianview); objectAnimator=ObjectAnimator.ofFloat(viewHolder.gpuAlphaView, "rotationY", 360).setDuration(5000); viewHolder.gpuAlphaView.setLayerType(View.LAYER_TYPE_HARDWARE,null); objectAnimator.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { viewHolder.gpuAlphaView.setLayerType(View.LAYER_TYPE_NONE,null); } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } }); objectAnimator.start(); // ObjectAnimator.ofFloat(viewHolder.gpuAlphaView, "rotationY", 360).setDuration(2500).start(); } return convertView; } private ViewHolder viewHolder=new ViewHolder(); @Override public void onAnimationStart(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } private class ViewHolder{ DurianGPUAlphaView gpuAlphaView; } }
DurianGPUAlphaView.java
package org.durian.duriangpuview.view; import android.animation.ObjectAnimator; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.RectF; import android.util.AttributeSet; import android.util.Log; import android.view.View; import android.view.ViewGroup; import org.durian.duriangpuview.R; /** * Project name : DurianGPUView * Created by zhibao.liu on 2016/1/15. * Time : 11:00 * Email [email protected] * Action : durian */ public class DurianGPUAlphaView extends View { private final static String TAG="DurianGPUAlphaView"; private Paint mPaint; private Bitmap bitmap; public DurianGPUAlphaView(Context context, AttributeSet attrs) { super(context, attrs); initView(context); } private void initView(Context context){ mPaint=new Paint(); mPaint.setColor(Color.RED); mPaint.setTextSize(128); if(!isInEditMode()){ Log.i(TAG,"not in edit mode !"); // setLayerType(View.LAYER_TYPE_HARDWARE,null); }else{ Log.i(TAG,"in edit mode !"); // setLayerType(View.LAYER_TYPE_NONE,null); } bitmap= BitmapFactory.decodeResource(context.getResources(), R.drawable.view); } @Override public boolean hasOverlappingRendering() { return false;//super.hasOverlappingRendering(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); // canvas.drawColor(0xff00ff00); canvas.drawBitmap(bitmap,25,0,null); canvas.drawText("DURIAN WORLD",300,175,mPaint); } }
durian_main.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context="org.durian.duriangpuview.DurianMainActivity"> <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="update list"/> <org.durian.duriangpuview.view.DurianGPUAlphaView android:id="@+id/gpuview" android:layout_width="wrap_content" android:layout_height="128dp" /> <View android:background="#0000ff" android:layout_width="match_parent" android:layout_height="4dp"/> <ListView android:id="@+id/android:list" android:layout_width="wrap_content" android:layout_height="wrap_content"/> </LinearLayout>
list_view.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <org.durian.duriangpuview.view.DurianGPUAlphaView android:id="@+id/durianview" android:layout_width="wrap_content" android:layout_height="128dp" android:background="@drawable/shadows"/> </LinearLayout>
shadows.xml 给自定义View套个阴影的效果
<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_pressed="true"> <layer-list> <item android:left="4dp" android:top="4dp"><shape> <solid android:color="#ff58bb52" /> <corners android:radius="30dip" /> </shape></item> </layer-list> </item> <item> <layer-list> <!-- 第一层 --> <item android:left="4dp" android:top="4dp"><shape> <solid android:color="#66000000" /> <corners android:radius="30dip" /> <!-- 描边 --> <stroke android:width="1dp" android:color="#ffffffff" /> </shape></item> <!-- 第二层 --> <item android:bottom="4dp" android:right="4dp"><shape> <solid android:color="#ff58bb52" /> <corners android:radius="30dip" /> <!-- 描边 --> <stroke android:width="1dp" android:color="#ffffffff" /> </shape></item> </layer-list></item> </selector>
<3> : 具体处理如下:
a> : 如果确定自定View一定要GPU渲染,那么在自定义View中增加GPU渲染设置:
if(!isInEditMode()){ Log.i(TAG,"not in edit mode !"); // setLayerType(View.LAYER_TYPE_HARDWARE,null); }else{ Log.i(TAG,"in edit mode !"); // setLayerType(View.LAYER_TYPE_NONE,null); }
把注释去掉.
b> : 在使用自定义View时,有很多地方需要做一下GPU加速渲染的操作,以保证运行流畅效果.比如在ListView中经常需要刷新:
@Override public View getView(int position, View convertView, ViewGroup parent) { Log.i(TAG,"*** getView position : "+position); if(convertView==null){ Log.i(TAG,"getView position : "+position); convertView=inflater.inflate(R.layout.list_view,null); viewHolder.gpuAlphaView=(DurianGPUAlphaView)convertView.findViewById(R.id.durianview); objectAnimator=ObjectAnimator.ofFloat(viewHolder.gpuAlphaView, "rotationY", 360).setDuration(5000); viewHolder.gpuAlphaView.setLayerType(View.LAYER_TYPE_HARDWARE,null); objectAnimator.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { viewHolder.gpuAlphaView.setLayerType(View.LAYER_TYPE_NONE,null); } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } }); objectAnimator.start(); // ObjectAnimator.ofFloat(viewHolder.gpuAlphaView, "rotationY", 360).setDuration(2500).start(); } return convertView; }
核心代码:
objectAnimator=ObjectAnimator.ofFloat(viewHolder.gpuAlphaView, "rotationY", 360).setDuration(5000); viewHolder.gpuAlphaView.setLayerType(View.LAYER_TYPE_HARDWARE,null);
objectAnimator.start();
动画播放完了就取消:
@Override public void onAnimationEnd(Animator animation) { viewHolder.gpuAlphaView.setLayerType(View.LAYER_TYPE_NONE,null); }
因为上面说了,GPU可以对界面上的原始数据直接做旋转,设置透明度等等操作!!!
c> : 当自定义View添加了阴影效果时:
@Override public boolean hasOverlappingRendering() { return false;//super.hasOverlappingRendering(); }
返回为false改善之.因为:
另外一个例子是包含阴影区域的View,这种类型的View并不会出现我们前面提到的问题,因为他们并不存在层叠的关系
例子源代码,可以自行运行看效果,今天这个东西好像无法上图:
http://pan.baidu.com/s/1blpUeq