圆角绘制 - 安卓R

阅读本文之前请先学会opengl的基本使用方法:LearnOpenGL CN (learnopengl-cn.github.io)

SurfaceFlinger中Layer的合成流程中,如果是gpu合成则会调用frameworks/native/libs/renderengine/gl/GLESRenderEngine.cpp的drawLayers方法:

status_t GLESRenderEngine::drawLayers(const DisplaySettings& display,
                                      const std::vector<const LayerSettings*>& layers,
                                      ANativeWindowBuffer* const buffer,
                                      const bool useFramebufferCache, base::unique_fd&& bufferFence,
                                      base::unique_fd* drawFence) {
    ......
    for (auto const layer : layers) {
        ......
        // We only want to do a special handling for rounded corners when having rounded corners
        // is the only reason it needs to turn on blending, otherwise, we handle it like the
        // usual way since it needs to turn on blending anyway.
        else if (layer->geometry.roundedCornersRadius > 0.0 && color.a >= 1.0f && isOpaque) {
            handleRoundedCorners(display, *layer, mesh);
        } else {
            drawMesh(mesh);
        }
        ......
    }
    ......
}

调用了GLESRenderEngine的handleRoundedCorners方法:

void GLESRenderEngine::handleRoundedCorners(const DisplaySettings& display,
                                            const LayerSettings& layer, const Mesh& mesh) {
    // We separate the layer into 3 parts essentially, such that we only turn on blending for the
    // top rectangle and the bottom rectangle, and turn off blending for the middle rectangle.
    FloatRect bounds = layer.geometry.roundedCornersCrop;

    // Explicitly compute the transform from the clip rectangle to the physical
    // display. Normally, this is done in glViewport but we explicitly compute
    // it here so that we can get the scissor bounds correct.
    const Rect& source = display.clip;
    const Rect& destination = display.physicalDisplay;
    // Here we compute the following transform:
    // 1. Translate the top left corner of the source clip to (0, 0)
    // 2. Rotate the clip rectangle about the origin in accordance with the
    // orientation flag
    // 3. Translate the top left corner back to the origin.
    // 4. Scale the clip rectangle to the destination rectangle dimensions
    // 5. Translate the top left corner to the destination rectangle's top left
    // corner.
    const mat4 translateSource = mat4::translate(vec4(-source.left, -source.top, 0, 1));
    mat4 rotation;
    int displacementX = 0;
    int displacementY = 0;
    float destinationWidth = static_cast<float>(destination.getWidth());
    float destinationHeight = static_cast<float>(destination.getHeight());
    float sourceWidth = static_cast<float>(source.getWidth());
    float sourceHeight = static_cast<float>(source.getHeight());
    const float rot90InRadians = 2.0f * static_cast<float>(M_PI) / 4.0f;
    switch (display.orientation) {
        case ui::Transform::ROT_90:
            rotation = mat4::rotate(rot90InRadians, vec3(0, 0, 1));
            displacementX = source.getHeight();
            std::swap(sourceHeight, sourceWidth);
            break;
        case ui::Transform::ROT_180:
            rotation = mat4::rotate(rot90InRadians * 2.0f, vec3(0, 0, 1));
            displacementY = source.getHeight();
            displacementX = source.getWidth();
            break;
        case ui::Transform::ROT_270:
            rotation = mat4::rotate(rot90InRadians * 3.0f, vec3(0, 0, 1));
            displacementY = source.getWidth();
            std::swap(sourceHeight, sourceWidth);
            break;
        default:
            break;
    }

    const mat4 intermediateTranslation = mat4::translate(vec4(displacementX, displacementY, 0, 1));
    const mat4 scale = mat4::scale(
            vec4(destinationWidth / sourceWidth, destinationHeight / sourceHeight, 1, 1));
    const mat4 translateDestination =
            mat4::translate(vec4(destination.left, destination.top, 0, 1));
    const mat4 globalTransform =
            translateDestination * scale * intermediateTranslation * rotation * translateSource;

    const mat4 transformMatrix = globalTransform * layer.geometry.positionTransform;
    const vec4 leftTopCoordinate(bounds.left, bounds.top, 1.0, 1.0);
    const vec4 rightBottomCoordinate(bounds.right, bounds.bottom, 1.0, 1.0);
    const vec4 leftTopCoordinateInBuffer = transformMatrix * leftTopCoordinate;
    const vec4 rightBottomCoordinateInBuffer = transformMatrix * rightBottomCoordinate;
    bounds = FloatRect(std::min(leftTopCoordinateInBuffer[0], rightBottomCoordinateInBuffer[0]),
                       std::min(leftTopCoordinateInBuffer[1], rightBottomCoordinateInBuffer[1]),
                       std::max(leftTopCoordinateInBuffer[0], rightBottomCoordinateInBuffer[0]),
                       std::max(leftTopCoordinateInBuffer[1], rightBottomCoordinateInBuffer[1]));

    // Finally, we cut the layer into 3 parts, with top and bottom parts having rounded corners
    // and the middle part without rounded corners.
    const int32_t radius = ceil(layer.geometry.roundedCornersRadius);
    const Rect topRect(bounds.left, bounds.top, bounds.right, bounds.top + radius);
    setScissor(topRect);
    drawMesh(mesh);
    const Rect bottomRect(bounds.left, bounds.bottom - radius, bounds.right, bounds.bottom);
    setScissor(bottomRect);
    drawMesh(mesh);

    // The middle part of the layer can turn off blending.
    if (topRect.bottom < bottomRect.top) {
        const Rect middleRect(bounds.left, bounds.top + radius, bounds.right,
                              bounds.bottom - radius);
        setScissor(middleRect);
        mState.cornerRadius = 0.0;
        disableBlending();
        drawMesh(mesh);
    }
    disableScissor();
}

在这里将layer裁剪成了3部分,其中中间部分不做处理,上部分和下部分将会裁剪出圆角。最后调用了GLESRenderEngine的drawMesh方法:

void GLESRenderEngine::drawMesh(const Mesh& mesh) {
    ......
    glVertexAttribPointer(Program::position, mesh.getVertexSize(), GL_FLOAT, GL_FALSE,
                          mesh.getByteStride(), mesh.getPositions());

    if (mState.cornerRadius > 0.0f) {
        glEnableVertexAttribArray(Program::cropCoords);
        glVertexAttribPointer(Program::cropCoords, mesh.getVertexSize(), GL_FLOAT, GL_FALSE,
                              mesh.getByteStride(), mesh.getCropCoords());
    }
    ......
    ProgramCache::getInstance().useProgram(mInProtectedContext ? mProtectedEGLContext : mEGLContext,
                                           managedState);

    if (mState.drawShadows) {
        glDrawElements(mesh.getPrimitive(), mesh.getIndexCount(), GL_UNSIGNED_SHORT,
                       mesh.getIndices());
    } else {
        glDrawArrays(mesh.getPrimitive(), 0, mesh.getVertexCount());
    }
    ......
    if (mState.cornerRadius > 0.0f) {
        glDisableVertexAttribArray(Program::cropCoords);
    }

    if (mState.drawShadows) {
        glDisableVertexAttribArray(Program::shadowColor);
        glDisableVertexAttribArray(Program::shadowParams);
    }
}

调用了frameworks/native/libs/renderengine/gl/ProgramCache.cpp的useProgram方法:

void ProgramCache::useProgram(EGLContext context, const Description& description) {
    // generate the key for the shader based on the description
    Key needs(computeKey(description));

    // look-up the program in the cache
    auto& cache = mCaches[context];
    auto it = cache.find(needs);
    if (it == cache.end()) {
        // we didn't find our program, so generate one...
        nsecs_t time = systemTime();
        it = cache.emplace(needs, generateProgram(needs)).first;
        time = systemTime() - time;

        ALOGV(">>> generated new program for context %p: needs=%08X, time=%u ms (%zu programs)",
              context, needs.mKey, uint32_t(ns2ms(time)), cache.size());
    }

    // here we have a suitable program for this description
    std::unique_ptr<Program>& program = it->second;
    if (program->isValid()) {
        program->use();
        program->setUniforms(description);
    }
}

这里通过ProgramCache的generateProgram方法加载shader:

std::unique_ptr<Program> ProgramCache::generateProgram(const Key& needs) {
    ATRACE_CALL();

    // vertex shader
    String8 vs = generateVertexShader(needs);

    // fragment shader
    String8 fs = generateFragmentShader(needs);

    return std::make_unique<Program>(needs, vs.string(), fs.string());
}

其中vertexShader将顶点的坐标准备好传给fragmentShader(这里cropCoords虽然不是顶点的坐标,但是通过打印log发现其与顶点坐标完全相同):

String8 ProgramCache::generateVertexShader(const Key& needs) {
    ......
    if (needs.hasRoundedCorners()) {
        vs << "attribute lowp vec4 cropCoords;";
        vs << "varying lowp vec2 outCropCoords;";
    }
    ......
        if (needs.hasRoundedCorners()) {
        vs << "outCropCoords = cropCoords.st;";
    }
    ......
}

然后在fragmentShader里面根据圆角的半径计算将在圆角之外的部分的alpha设为0使之完全透明:

String8 ProgramCache::generateFragmentShader(const Key& needs) {
    ......
    if (needs.hasRoundedCorners()) {
        // Rounded corners implementation using a signed distance function.
        fs << R"__SHADER__(
            uniform float cornerRadius;
            uniform vec2 cropCenter;
            varying vec2 outCropCoords;

            /**
             * This function takes the current crop coordinates and calculates an alpha value based
             * on the corner radius and distance from the crop center.
             */
            float applyCornerRadius(vec2 cropCoords)
            {
                vec2 position = cropCoords - cropCenter;
                // Scale down the dist vector here, as otherwise large corner
                // radii can cause floating point issues when computing the norm
                vec2 dist = (abs(position) - cropCenter + vec2(cornerRadius)) / 16.0;
                // Once we've found the norm, then scale back up.
                float plane = length(max(dist, vec2(0.0))) * 16.0;
                return 1.0 - clamp(plane - cornerRadius, 0.0, 1.0);
            }
            )__SHADER__";
    }
    ......
    if (needs.hasRoundedCorners()) {
        ......
            fs << "gl_FragColor.a *= applyCornerRadius(outCropCoords);";
        ......
    }
    ......
}

其中dist是从圆角的圆的圆心到当前像素坐标cropCoords的矢量。

就这样在圆角的圆之外的区域的alpha被设为了0,从而使得矩形窗口的四个角是圆的。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
使用 MPAndroidChart 绘制横向圆角柱状图的步骤如下: 1. 在你的项目中添加 MPAndroidChart 的依赖。 2. 在 XML 布局文件中添加一个 BarChart 控件。 3. 在代码中获取 BarChart 控件的实例,并进行一些基础设置,如设置 X 轴和 Y 轴的属性。 4. 创建一个 BarDataSet 对象,并设置数据。 5. 创建一个 BarData 对象,并将 BarDataSet 对象添加到 BarData 对象中。 6. 设置 BarData 对象到 BarChart 控件中,并进行一些样式设置,如设置柱状图的颜色、边框宽度等。 7. 最后调用 BarChart 控件的 invalidate() 方法刷新界面即可。 下面是一个示例代码,可以绘制横向圆角柱状图: ```java // 获取 BarChart 控件实例 BarChart barChart = findViewById(R.id.bar_chart); // 设置 X 轴和 Y 轴属性 barChart.getXAxis().setPosition(XAxis.XAxisPosition.BOTTOM); barChart.getXAxis().setDrawGridLines(false); barChart.getAxisLeft().setDrawGridLines(false); barChart.getAxisRight().setDrawGridLines(false); // 创建 BarDataSet 对象并设置数据 List<BarEntry> entries = new ArrayList<>(); entries.add(new BarEntry(0f, 5f)); entries.add(new BarEntry(1f, 6f)); entries.add(new BarEntry(2f, 7f)); entries.add(new BarEntry(3f, 8f)); entries.add(new BarEntry(4f, 9f)); BarDataSet dataSet = new BarDataSet(entries, "数据"); // 创建 BarData 对象并将 BarDataSet 添加到其中 BarData data = new BarData(dataSet); // 设置 BarData 到 BarChart 控件中 barChart.setData(data); // 设置柱状图的颜色、边框宽度等 dataSet.setColors(Color.parseColor("#3F51B5")); dataSet.setDrawValues(false); dataSet.setBarBorderWidth(1f); dataSet.setBarBorderColor(Color.parseColor("#3F51B5")); dataSet.setBarBorderRadius(20f); // 刷新界面 barChart.invalidate(); ``` 在上面的示例代码中,我们设置了一个数据集合,包含了 5 个柱状图的数据(横坐标分别是 0 到 4),并设置了柱状图的样式,最后调用 invalidate() 方法刷新界面即可。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

SSSxCCC

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值