Android OpenGL ES 2.0学习研究 (一)

来源:互联网 发布:ubuntu解压rar文件 编辑:程序博客网 时间:2024/06/02 12:47

Android OpenGL ES 2.0学习研究 (一)

基于对 Google 的 Gallery 代码的研究和修改,对 OpenGL ES 2.0 在 Android 中的使用进行总结;

这一篇主要集中于四点进行简要介绍:GLRootView(base) + GLView(UI) + GLES20Canvas(canvas) + Texture;

关于OpenGL ES的基础知识可以参考:OpenGL ES 简明教程

概述:

目前,关于GLES的教程也有不少,但是基本上都是基础知识介绍,很零散;

Google官方的Gallery则对代码进行了封装,结构更加清晰灵活,非常适合学习研究GLES;

GLRootView(base)+ GLES20Canvas(canvas) + GLView(ui) + Texture:

  • GLRootView是一个GLSurfaceView,通过GLSurfaceView.Renderer的三大方法将GLView绘制到GLCanvas上:

    • GLSurfaceView:
      • 起到连接 OpenGL ES与Android 的 View 层次结构之间的桥梁作用。
      • 使得 Open GL ES 库适应于 Anndroid 系统的 Activity 生命周期。
      • 使得选择合适的 Frame buffer 像素格式变得容易。
      • 创建和管理单独绘图线程以达到平滑动画效果。
      • 提供了方便使用的调试工具来跟踪 OpenGL ES 函数调用以帮助检查错误。
      • public void setRenderer(GLSurfaceView.Renderer renderer)
    • GLSurfaceView.Renderer:

      • public void onSurfaceCreated(GL10 gl, EGLConfig config)
      • public void onDrawFrame(GL10 gl)
      • public void onSurfaceChanged(GL10 gl, int width, int height)
    • GLES20Canvas相当于Canvas,设置好Vertex shader和Fragment shader等参数,将GLView绘制出来:

    • GLES的使用都集中在这里处理,相当于从GLSurfaceView中抽离出来GLES的代码进行封装处理;
  • GLView是要绘制的UI,可以同时有多个,它处理触摸事件,通过GLRootView将其绘制在GLCanvas上:

    • GLView是整个UI的布局;
    • 可以在GLView基础上进行继承,充分自定义;
  • Texture则是绘制的画面,在GLES20Canvas上绘制出来:

    • 可以将其细分为ColorTexture、StringTexture、BitmapTexture分别绘制色块、字符串和图片;

接下来,看代码,具体说明看备注:

自定义一个GLRootView,继承GLSurfaceView并实现GLSurfaceView.Renderer接口(GLRoot为自定义接口)

//GLRoot为自定义接口public class GLRootView extends GLSurfaceView        implements GLSurfaceView.Renderer, GLRoot {    public GLRootView(Context context) {        this(context, null);    }    public GLRootView(Context context, AttributeSet attrs) {        super(context, attrs);        //进行初始化设置        setBackgroundDrawable(null);        setEGLContextClientVersion(2);        setEGLConfigChooser(mEglConfigChooser);        setRenderer(this);        if (Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) {            getHolder().setFormat(PixelFormat.RGB_888);        } else {            getHolder().setFormat(PixelFormat.RGB_565);        }    }    @Override    public void onSurfaceCreated(GL10 gl1, EGLConfig config) {        GL11 gl = (GL11) gl1;        mRenderLock.lock();        try {            mGL = gl;            //创建GLES20Canvas,创建的时候会自动加载shader            mCanvas = new GLES20Canvas();            BasicTexture.invalidateAllTextures();        } finally {            mRenderLock.unlock();        }        //设置渲染模式为 RENDERMODE_WHEN_DIRTY 以节约性能        setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);      //setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);    }    @Override    public void onSurfaceChanged(GL10 gl1, int width, int height) {        //设置线程        Process.setThreadPriority(Process.THREAD_PRIORITY_DISPLAY);        GalleryUtils.setRenderThread();        GL11 gl = (GL11) gl1;        Utils.assertTrue(mGL == gl);        //重新设置GLES20Canvas的尺寸        mCanvas.setSize(width, height);    }    @Override    public void onDrawFrame(GL10 gl) {        AnimationTime.update();        mRenderLock.lock();        try {            //重点在这,具体的绘制操作        } finally {            mRenderLock.unlock();        }        //第一次绘制的时候放置一个黑色的背景,以防透明        if (mFirstDraw) {            mFirstDraw = false;            post(new Runnable() {                    @Override                    public void run() {                        View root = getRootView();                        View cover = root.findViewById(R.id.gl_root_cover);                        cover.setVisibility(GONE);                    }                });        }    }    //处理触摸事件    @Override    public boolean dispatchTouchEvent(MotionEvent event) {        if (!isEnabled()) return false;        int action = event.getAction();        if (action == MotionEvent.ACTION_CANCEL                || action == MotionEvent.ACTION_UP) {            mInDownState = false;        } else if (!mInDownState && action != MotionEvent.ACTION_DOWN) {            return false;        }        mRenderLock.lock();        try {            // If this has been detached from root, we don't need to handle event            boolean handled = mContentView != null                    && mContentView.dispatchTouchEvent(event);            if (action == MotionEvent.ACTION_DOWN && handled) {                mInDownState = true;            }            return handled;        } finally {            mRenderLock.unlock();        }    }}

自定义GLES20Canvas

@SuppressLint("NewApi")public class GLES20Canvas implements GLCanvas {        private abstract static class ShaderParameter {        public int handle;        protected final String mName;        public ShaderParameter(String name) {            mName = name;        }        public abstract void loadHandle(int program);    }    //处理shader    private static class UniformShaderParameter extends ShaderParameter {        public UniformShaderParameter(String name) {            super(name);        }        @Override        public void loadHandle(int program) {            handle = GLES20.glGetUniformLocation(program, mName);            checkError();        }    }    private static class AttributeShaderParameter extends ShaderParameter {        public AttributeShaderParameter(String name) {            super(name);        }        @Override        public void loadHandle(int program) {            handle = GLES20.glGetAttribLocation(program, mName);            checkError();        }    }    //初始化    public GLES20Canvas() {        Matrix.setIdentityM(mTempTextureMatrix, 0);        Matrix.setIdentityM(mMatrices, mCurrentMatrixIndex);        FloatBuffer boxBuffer = createBuffer(BOX_COORDINATES);        mBoxCoordinates = uploadBuffer(boxBuffer);        //创建的时候就加载shader        int drawVertexShader = loadShader(GLES20.GL_VERTEX_SHADER, DRAW_VERTEX_SHADER);        int textureVertexShader = loadShader(GLES20.GL_VERTEX_SHADER, TEXTURE_VERTEX_SHADER);        int meshVertexShader = loadShader(GLES20.GL_VERTEX_SHADER, MESH_VERTEX_SHADER);        int drawFragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, DRAW_FRAGMENT_SHADER);        int textureFragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, TEXTURE_FRAGMENT_SHADER);        int oesTextureFragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER,                OES_TEXTURE_FRAGMENT_SHADER);        mDrawProgram = assembleProgram(drawVertexShader, drawFragmentShader, mDrawParameters);        mTextureProgram = assembleProgram(textureVertexShader, textureFragmentShader,                mTextureParameters);        mOesTextureProgram = assembleProgram(textureVertexShader, oesTextureFragmentShader,                mOesTextureParameters);        mMeshProgram = assembleProgram(meshVertexShader, textureFragmentShader, mMeshParameters);        GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_ALPHA);        checkError();    }    @Override    public void setSize(int width, int height) {        mWidth = width;        mHeight = height;        GLES20.glViewport(0, 0, mWidth, mHeight);        checkError();        Matrix.setIdentityM(mMatrices, mCurrentMatrixIndex);        Matrix.orthoM(mProjectionMatrix, 0, 0, width, 0, height, -1, 1);        if (getTargetTexture() == null) {            mScreenWidth = width;            mScreenHeight = height;            Matrix.translateM(mMatrices, mCurrentMatrixIndex, 0, height, 0);            Matrix.scaleM(mMatrices, mCurrentMatrixIndex, 1, -1, 1);        }    }    private void draw(int type, int offset, int count, float x, float y, float width, float height,            int color, float lineWidth) {        prepareDraw(offset, color, lineWidth);        draw(mDrawParameters, type, count, x, y, width, height);    }    //设置背景颜色    private void prepareDraw(int offset, int color, float lineWidth) {        GLES20.glUseProgram(mDrawProgram);        checkError();        if (lineWidth > 0) {            GLES20.glLineWidth(lineWidth);            checkError();        }        float[] colorArray = getColor(color);        boolean blendingEnabled = (colorArray[3] < 1f);        enableBlending(blendingEnabled);        if (blendingEnabled) {            GLES20.glBlendColor(colorArray[0], colorArray[1], colorArray[2], colorArray[3]);            checkError();        }        GLES20.glUniform4fv(mDrawParameters[INDEX_COLOR].handle, 1, colorArray, 0);        setPosition(mDrawParameters, offset);        checkError();    }    //绘制    private void draw(ShaderParameter[] params, int type, int count, float x, float y, float width,            float height) {        setMatrix(params, x, y, width, height);        int positionHandle = params[INDEX_POSITION].handle;        GLES20.glEnableVertexAttribArray(positionHandle);        checkError();        GLES20.glDrawArrays(type, 0, count);        checkError();        GLES20.glDisableVertexAttribArray(positionHandle);        checkError();    }    public static void checkError() {        int error = GLES20.glGetError();        if (error != 0) {            Throwable t = new Throwable();            Log.e(TAG, "GL error: " + error, t);        }    }    //以下几个方法均为BitmapTexture调用    @Override    public void setTextureParameters(BasicTexture texture) {        int target = texture.getTarget();        GLES20.glBindTexture(target, texture.getId());        checkError();        GLES20.glTexParameteri(target, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);        GLES20.glTexParameteri(target, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);        GLES20.glTexParameterf(target, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);        GLES20.glTexParameterf(target, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);    }    @Override    public void initializeTextureSize(BasicTexture texture, int format, int type) {        int target = texture.getTarget();        GLES20.glBindTexture(target, texture.getId());        checkError();        int width = texture.getTextureWidth();        int height = texture.getTextureHeight();        GLES20.glTexImage2D(target, 0, format, width, height, 0, format, type, null);    }    @Override    public void initializeTexture(BasicTexture texture, Bitmap bitmap) {        int target = texture.getTarget();        GLES20.glBindTexture(target, texture.getId());        checkError();        GLUtils.texImage2D(target, 0, bitmap, 0);    }    @Override    public void texSubImage2D(BasicTexture texture, int xOffset, int yOffset, Bitmap bitmap,            int format, int type) {        int target = texture.getTarget();        GLES20.glBindTexture(target, texture.getId());        checkError();        GLUtils.texSubImage2D(target, 0, xOffset, yOffset, bitmap, format, type);    }}

自定义GLView

public class GLView {    public void startAnimation(CanvasAnimation animation) {        GLRoot root = getGLRoot();        if (root == null) throw new IllegalStateException();        mAnimation = animation;        if (mAnimation != null) {            mAnimation.start();            root.registerLaunchedAnimation(mAnimation);        }        invalidate();    }    // Sets the visiblity of this GLView (either GLView.VISIBLE or    // GLView.INVISIBLE).    public void setVisibility(int visibility) {        if (visibility == getVisibility()) return;        if (visibility == VISIBLE) {            mViewFlags &= ~FLAG_INVISIBLE;        } else {            mViewFlags |= FLAG_INVISIBLE;        }        onVisibilityChanged(visibility);        invalidate();    }    // Returns GLView.VISIBLE or GLView.INVISIBLE    public int getVisibility() {        return (mViewFlags & FLAG_INVISIBLE) == 0 ? VISIBLE : INVISIBLE;    }    // This should only be called on the content pane (the topmost GLView).    public void attachToRoot(GLRoot root) {        Utils.assertTrue(mParent == null && mRoot == null);        onAttachToRoot(root);    }    // This should only be called on the content pane (the topmost GLView).    public void detachFromRoot() {        Utils.assertTrue(mParent == null && mRoot != null);        onDetachFromRoot();    }    // Returns the number of children of the GLView.    public int getComponentCount() {        return mComponents == null ? 0 : mComponents.size();    }    // Returns the children for the given index.    public GLView getComponent(int index) {        if (mComponents == null) {            throw new ArrayIndexOutOfBoundsException(index);        }        return mComponents.get(index);    }    // Adds a child to this GLView.    public void addComponent(GLView component) {        // Make sure the component doesn't have a parent currently.        if (component.mParent != null) throw new IllegalStateException();        // Build parent-child links        if (mComponents == null) {            mComponents = new ArrayList<GLView>();        }        mComponents.add(component);        component.mParent = this;        // If this is added after we have a root, tell the component.        if (mRoot != null) {            component.onAttachToRoot(mRoot);        }    }    // Removes a child from this GLView.    public boolean removeComponent(GLView component) {        if (mComponents == null) return false;        if (mComponents.remove(component)) {            removeOneComponent(component);            return true;        }        return false;    }    // Removes all children of this GLView.    public void removeAllComponents() {        for (int i = 0, n = mComponents.size(); i < n; ++i) {            removeOneComponent(mComponents.get(i));        }        mComponents.clear();    }    private void removeOneComponent(GLView component) {        if (mMotionTarget == component) {            long now = SystemClock.uptimeMillis();            MotionEvent cancelEvent = MotionEvent.obtain(                    now, now, MotionEvent.ACTION_CANCEL, 0, 0, 0);            dispatchTouchEvent(cancelEvent);            cancelEvent.recycle();        }        component.onDetachFromRoot();        component.mParent = null;    }    public Rect bounds() {        return mBounds;    }    public int getWidth() {        return mBounds.right - mBounds.left;    }    public int getHeight() {        return mBounds.bottom - mBounds.top;    }    public GLRoot getGLRoot() {        return mRoot;    }    // Request re-rendering of the view hierarchy.    // This is used for animation or when the contents changed.    public void invalidate() {        GLRoot root = getGLRoot();        if (root != null) root.requestRender();    }    // Request re-layout of the view hierarchy.    public void requestLayout() {        mViewFlags |= FLAG_LAYOUT_REQUESTED;        mLastHeightSpec = -1;        mLastWidthSpec = -1;        if (mParent != null) {            mParent.requestLayout();        } else {            // Is this a content pane ?            GLRoot root = getGLRoot();            if (root != null) root.requestLayoutContentPane();        }    }    protected void render(GLCanvas canvas) {        boolean transitionActive = false;        if (mTransition != null && mTransition.calculate(AnimationTime.get())) {            invalidate();            transitionActive = mTransition.isActive();        }        renderBackground(canvas);        canvas.save();        if (transitionActive) {            mTransition.applyContentTransform(this, canvas);        }        for (int i = 0, n = getComponentCount(); i < n; ++i) {            renderChild(canvas, getComponent(i));        }        canvas.restore();        if (transitionActive) {            mTransition.applyOverlay(this, canvas);        }    }    public void setIntroAnimation(StateTransitionAnimation intro) {        mTransition = intro;        if (mTransition != null) mTransition.start();    }    public float [] getBackgroundColor() {        return mBackgroundColor;    }    public void setBackgroundColor(float [] color) {        mBackgroundColor = color;    }    protected void renderBackground(GLCanvas view) {        if (mBackgroundColor != null) {            view.clearBuffer(mBackgroundColor);        }        if (mTransition != null && mTransition.isActive()) {            mTransition.applyBackground(this, view);            return;        }    }    protected void renderChild(GLCanvas canvas, GLView component) {        if (component.getVisibility() != GLView.VISIBLE                && component.mAnimation == null) return;        int xoffset = component.mBounds.left - mScrollX;        int yoffset = component.mBounds.top - mScrollY;        canvas.translate(xoffset, yoffset);        CanvasAnimation anim = component.mAnimation;        if (anim != null) {            canvas.save(anim.getCanvasSaveFlags());            if (anim.calculate(AnimationTime.get())) {                invalidate();            } else {                component.mAnimation = null;            }            anim.apply(canvas);        }        component.render(canvas);        if (anim != null) canvas.restore();        canvas.translate(-xoffset, -yoffset);    }    protected boolean onTouch(MotionEvent event) {        return false;    }    protected boolean dispatchTouchEvent(MotionEvent event,            int x, int y, GLView component, boolean checkBounds) {        Rect rect = component.mBounds;        int left = rect.left;        int top = rect.top;        if (!checkBounds || rect.contains(x, y)) {            event.offsetLocation(-left, -top);            if (component.dispatchTouchEvent(event)) {                event.offsetLocation(left, top);                return true;            }            event.offsetLocation(left, top);        }        return false;    }    protected boolean dispatchTouchEvent(MotionEvent event) {        int x = (int) event.getX();        int y = (int) event.getY();        int action = event.getAction();        if (mMotionTarget != null) {            if (action == MotionEvent.ACTION_DOWN) {                MotionEvent cancel = MotionEvent.obtain(event);                cancel.setAction(MotionEvent.ACTION_CANCEL);                dispatchTouchEvent(cancel, x, y, mMotionTarget, false);                mMotionTarget = null;            } else {                dispatchTouchEvent(event, x, y, mMotionTarget, false);                if (action == MotionEvent.ACTION_CANCEL                        || action == MotionEvent.ACTION_UP) {                    mMotionTarget = null;                }                return true;            }        }        if (action == MotionEvent.ACTION_DOWN) {            // in the reverse rendering order            for (int i = getComponentCount() - 1; i >= 0; --i) {                GLView component = getComponent(i);                if (component.getVisibility() != GLView.VISIBLE) continue;                if (dispatchTouchEvent(event, x, y, component, true)) {                    mMotionTarget = component;                    return true;                }            }        }        return onTouch(event);    }    public Rect getPaddings() {        return mPaddings;    }    public void layout(int left, int top, int right, int bottom) {        boolean sizeChanged = setBounds(left, top, right, bottom);        mViewFlags &= ~FLAG_LAYOUT_REQUESTED;        // We call onLayout no matter sizeChanged is true or not because the        // orientation may change without changing the size of the View (for        // example, rotate the device by 180 degrees), and we want to handle        // orientation change in onLayout.        onLayout(sizeChanged, left, top, right, bottom);    }    private boolean setBounds(int left, int top, int right, int bottom) {        boolean sizeChanged = (right - left) != (mBounds.right - mBounds.left)                || (bottom - top) != (mBounds.bottom - mBounds.top);        mBounds.set(left, top, right, bottom);        return sizeChanged;    }    public void measure(int widthSpec, int heightSpec) {        if (widthSpec == mLastWidthSpec && heightSpec == mLastHeightSpec                && (mViewFlags & FLAG_LAYOUT_REQUESTED) == 0) {            return;        }        mLastWidthSpec = widthSpec;        mLastHeightSpec = heightSpec;        mViewFlags &= ~FLAG_SET_MEASURED_SIZE;        onMeasure(widthSpec, heightSpec);        if ((mViewFlags & FLAG_SET_MEASURED_SIZE) == 0) {            throw new IllegalStateException(getClass().getName()                    + " should call setMeasuredSize() in onMeasure()");        }    }    protected void onMeasure(int widthSpec, int heightSpec) {    }    protected void setMeasuredSize(int width, int height) {        mViewFlags |= FLAG_SET_MEASURED_SIZE;        mMeasuredWidth = width;        mMeasuredHeight = height;    }    public int getMeasuredWidth() {        return mMeasuredWidth;    }    public int getMeasuredHeight() {        return mMeasuredHeight;    }    protected void onLayout(            boolean changeSize, int left, int top, int right, int bottom) {    }    /**     * Gets the bounds of the given descendant that relative to this view.     */    public boolean getBoundsOf(GLView descendant, Rect out) {        int xoffset = 0;        int yoffset = 0;        GLView view = descendant;        while (view != this) {            if (view == null) return false;            Rect bounds = view.mBounds;            xoffset += bounds.left;            yoffset += bounds.top;            view = view.mParent;        }        out.set(xoffset, yoffset, xoffset + descendant.getWidth(),                yoffset + descendant.getHeight());        return true;    }    protected void onVisibilityChanged(int visibility) {        for (int i = 0, n = getComponentCount(); i < n; ++i) {            GLView child = getComponent(i);            if (child.getVisibility() == GLView.VISIBLE) {                child.onVisibilityChanged(visibility);            }        }    }    protected void onAttachToRoot(GLRoot root) {        mRoot = root;        for (int i = 0, n = getComponentCount(); i < n; ++i) {            getComponent(i).onAttachToRoot(root);        }    }    protected void onDetachFromRoot() {        for (int i = 0, n = getComponentCount(); i < n; ++i) {            getComponent(i).onDetachFromRoot();        }        mRoot = null;    }    public void lockRendering() {        if (mRoot != null) {            mRoot.lockRenderThread();        }    }    public void unlockRendering() {        if (mRoot != null) {            mRoot.unlockRenderThread();        }    }}

自定义Texture

ColorTexture

// ColorTexture 就是一个填充特定颜色的色块,代码很简单,包括颜色和尺寸,已经绘制的方法;public class ColorTexture implements Texture {    private final int mColor;    private int mWidth;    private int mHeight;    public ColorTexture(int color) {        mColor = color;        mWidth = 1;        mHeight = 1;    }    @Override    public void draw(GLCanvas canvas, int x, int y) {        draw(canvas, x, y, mWidth, mHeight);    }    @Override    public void draw(GLCanvas canvas, int x, int y, int w, int h) {        canvas.fillRect(x, y, w, h, mColor);    }    @Override    public boolean isOpaque() {        return Utils.isOpaque(mColor);    }    public void setSize(int width, int height) {        mWidth = width;        mHeight = height;    }    @Override    public int getWidth() {        return mWidth;    }    @Override    public int getHeight() {        return mHeight;    }}

StringTexture

// StringTexture 提供了文本内容的绘制方法,可以设置文本内容、字体大小,颜色,也很简单;public class StringTexture extends CanvasTexture {    private final String mText;    private final TextPaint mPaint;    private final FontMetricsInt mMetrics;    private StringTexture(String text, TextPaint paint,            FontMetricsInt metrics, int width, int height) {        super(width, height);        mText = text;        mPaint = paint;        mMetrics = metrics;    }    public static TextPaint getDefaultPaint(float textSize, int color) {        TextPaint paint = new TextPaint();        paint.setTextSize(textSize);        paint.setAntiAlias(true);        paint.setColor(color);        paint.setShadowLayer(2f, 0f, 0f, Color.BLACK);        return paint;    }    public static StringTexture newInstance(            String text, float textSize, int color) {        return newInstance(text, getDefaultPaint(textSize, color));    }    public static StringTexture newInstance(            String text, float textSize, int color,            float lengthLimit, boolean isBold) {        TextPaint paint = getDefaultPaint(textSize, color);        if (isBold) {            paint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));        }        if (lengthLimit > 0) {            text = TextUtils.ellipsize(                    text, paint, lengthLimit, TextUtils.TruncateAt.END).toString();        }        return newInstance(text, paint);    }    private static StringTexture newInstance(String text, TextPaint paint) {        FontMetricsInt metrics = paint.getFontMetricsInt();        int width = (int) Math.ceil(paint.measureText(text));        int height = metrics.bottom - metrics.top;        // The texture size needs to be at least 1x1.        if (width <= 0) width = 1;        if (height <= 0) height = 1;        return new StringTexture(text, paint, metrics, width, height);    }    @Override    protected void onDraw(Canvas canvas, Bitmap backing) {        canvas.translate(0, -mMetrics.ascent);        canvas.drawText(mText, 0, 0, mPaint);    }}

BitmapTexture

// BitmapTexture 提供了图片的绘制方法,细节都在UploadedTexture里,这是处理起来最麻烦的Texture了public class BitmapTexture extends UploadedTexture {    protected Bitmap mContentBitmap;    public BitmapTexture(Bitmap bitmap) {        this(bitmap, false);    }    public BitmapTexture(Bitmap bitmap, boolean hasBorder) {        super(hasBorder);        Assert.assertTrue(bitmap != null && !bitmap.isRecycled());        mContentBitmap = bitmap;    }    @Override    protected void onFreeBitmap(Bitmap bitmap) {        if (!inFinalizer()) {            bitmap.recycle();        }    }    @Override    protected Bitmap onGetBitmap() {        return mContentBitmap;    }    public Bitmap getBitmap() {        return mContentBitmap;    }}

说明

下一篇会细化介绍

0 0
原创粉丝点击