常常在开发过程中使用自定义的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 warden_sprite@foxmail.com * 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 warden_sprite@foxmail.com
* 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