Android 自定义图片形状 圆形到多边形

最近因为项目需要找到这个demo ,顺便分享给大家。自定义图片的形状及旋转方向。

 

下面是自定义内容

package net.grobas.view;


import net.grobas.polygonimageview.sample.R;
import net.grobas.shapes.PolygonShape;
import net.grobas.shapes.PolygonShapeSpec;
import net.grobas.shapes.RegularPolygonShape;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.CornerPathEffect;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Shader;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.media.ThumbnailUtils;
import android.net.Uri;
import android.os.Build;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.ImageView;


/**
 * Construct a custom ImageView with a regular polygonal form.
 * The number of vertices determines the polygon form.
 * Special cases for vertex number are:
 * 0 -> Circle
 * 1 -> Regular ImageView
 * 2 -> Square
 * Use square images
 *
 * @author Albert Grobas
 */
public class PolygonImageView extends ImageView {


    //draws
    private Paint mPaint;
    private Paint mBorderPaint;
    private Path mPath;


    private PolygonShape mPolygonShape;
    private PolygonShapeSpec mPolygonShapeSpec;


    private int canvasWidth, canvasHeight;
    String LAYER_TYPE_SOFTWARE = "";


    public PolygonImageView(Context context) {
        this(context, null);
    }


    public PolygonImageView(Context context, AttributeSet attrs) {
        this(context, attrs, R.attr.polygonImageViewStyle);
    }


    public PolygonImageView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);


        mPolygonShapeSpec = new PolygonShapeSpec();


        TypedArray attributes = context.getTheme().obtainStyledAttributes(attrs,
                R.styleable.PolygonImageView, defStyle, 0);


        try {
            mPolygonShapeSpec.setRotation(attributes.getFloat(R.styleable.PolygonImageView_poly_rotation_angle, 0f));
            mPolygonShapeSpec.setNumVertex(attributes.getInteger(R.styleable.PolygonImageView_poly_vertices, 6));
            mPolygonShapeSpec.setCornerRadius(attributes.getFloat(R.styleable.PolygonImageView_poly_corner_radius, 0f));
            mPolygonShapeSpec.setHasShadow(attributes.getBoolean(R.styleable.PolygonImageView_poly_shadow, false));
            mPolygonShapeSpec.setShadowColor(attributes.getColor(R.styleable.PolygonImageView_poly_shadow_color, Color.BLACK));
            mPolygonShapeSpec.setHasBorder(attributes.getBoolean(R.styleable.PolygonImageView_poly_border, false));
            mPolygonShapeSpec.setBorderColor(attributes.getColor(R.styleable.PolygonImageView_poly_border_color, Color.WHITE));
            mPolygonShapeSpec.setBorderWidth(attributes.getDimension(R.styleable.PolygonImageView_poly_border_width, 4));


        } finally {
            attributes.recycle();
        }


        init();
    }


    /**
     * Init paints and effects
     */
    @SuppressLint("NewApi")
    private void init() {
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setPathEffect(new CornerPathEffect(mPolygonShapeSpec.getCornerRadius()));
        mBorderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mBorderPaint.setStyle(Paint.Style.STROKE);
        mBorderPaint.setPathEffect(new CornerPathEffect(mPolygonShapeSpec.getCornerRadius()));


        if(mPolygonShapeSpec.hasBorder()) {
            mBorderPaint.setColor(mPolygonShapeSpec.getBorderColor());
            mBorderPaint.setStrokeWidth(mPolygonShapeSpec.getBorderWidth());
        }


        if (mPolygonShapeSpec.hasShadow()) {
            //Shadow on border even if isBordered is false. Better effect and performance that
            //using shadow on main paint
            mBorderPaint.setShadowLayer(mPolygonShapeSpec.getShadowRadius(), mPolygonShapeSpec.getShadowXOffset(),
                    mPolygonShapeSpec.getShadowYOffset(), mPolygonShapeSpec.getShadowColor());
        }


//        //Avoid known shadow problems
//        if (Build.VERSION.SDK_INT > 13)
//            setLayerType(LAYER_TYPE_SOFTWARE, null);


        mPolygonShape = new RegularPolygonShape();
    }


    /**
     * Gets incoming new canvas size and updates polygon form and image if needed.
     *
     * @param w    new Width
     * @param h    new Height
     * @param oldW old Width
     * @param oldH old Height
     */
    @Override
    protected void onSizeChanged(int w, int h, int oldW, int oldH) {
        super.onSizeChanged(w, h, oldW, oldH);


        canvasWidth = w;
        canvasHeight = h;
        updatePolygonSize();


        if (Math.min(canvasWidth, canvasHeight) != Math.min(oldW, oldH)) {
            refreshImage();
        }
    }


    /**
     * Force Override to solve bug on Lollipop
     *
     * @param widthMeasureSpec  Width Spec Measure
     * @param heightMeasureSpec Height Spec Measure
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int width = measureWidth(widthMeasureSpec);
        int height = measureHeight(heightMeasureSpec);
        setMeasuredDimension(width, height);
    }


    private int measureWidth(int measureSpecWidth) {
        return measure(measureSpecWidth);
    }


    private int measureHeight(int measureSpecHeight) {
        //Force do not square measure to solve bug (use base 2 better performance)
        return (measure(measureSpecHeight) + 2);
    }


    private int measure(int measureSpec) {
        int result;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);


        if (specMode == MeasureSpec.EXACTLY || specMode == MeasureSpec.AT_MOST) {
            result = specSize;
        } else {
            result = Math.min(canvasWidth, canvasHeight);
        }


        return result;
    }


    /**
     * Draw the polygon form.
     *
     * @param canvas main canvas
     */
    @Override
    protected void onDraw(Canvas canvas) {
        if (getDrawable() == null || getDrawable().getIntrinsicWidth() == 0 ||
                getDrawable().getIntrinsicHeight() == 0)
            return;


        switch (mPolygonShapeSpec.getNumVertex()) {
            case 0: //CIRCLE
                if (mPolygonShapeSpec.hasShadow() || mPolygonShapeSpec.hasBorder()) {
                    canvas.drawCircle(mPolygonShapeSpec.getCenterX(), mPolygonShapeSpec.getCenterY(),
                            mPolygonShapeSpec.getDiameter() / 2, mBorderPaint);
                }
                canvas.drawCircle(mPolygonShapeSpec.getCenterX(), mPolygonShapeSpec.getCenterY(),
                        mPolygonShapeSpec.getDiameter() / 2, mPaint);
                break;
            case 1: //REGULAR IMAGE VIEW
                super.onDraw(canvas);
                break;
            case 2: //SQUARE
                if (mPolygonShapeSpec.hasShadow() || mPolygonShapeSpec.hasBorder()) {
                    canvas.drawRect(mPolygonShapeSpec.getCenterX() - mPolygonShapeSpec.getDiameter() / 2,
                        mPolygonShapeSpec.getCenterY() - mPolygonShapeSpec.getDiameter() / 2,
                        mPolygonShapeSpec.getCenterX() + mPolygonShapeSpec.getDiameter() / 2,
                        mPolygonShapeSpec.getCenterY() + mPolygonShapeSpec.getDiameter() / 2,
                        mBorderPaint);
                }
                canvas.drawRect(mPolygonShapeSpec.getCenterX() - mPolygonShapeSpec.getDiameter() / 2,
                    mPolygonShapeSpec.getCenterY() - mPolygonShapeSpec.getDiameter() / 2,
                    mPolygonShapeSpec.getCenterX() + mPolygonShapeSpec.getDiameter() / 2,
                    mPolygonShapeSpec.getCenterY() + mPolygonShapeSpec.getDiameter() / 2,
                    mPaint);
                break;
            default: //POLYGON
                if (mPolygonShapeSpec.hasShadow() || mPolygonShapeSpec.hasBorder())
                    canvas.drawPath(mPath, mBorderPaint);
                canvas.drawPath(mPath, mPaint);
        }
    }


    /**
     * Take cares about padding changes.
     *
     * @param start  start
     * @param top    top
     * @param end    end
     * @param bottom bottom
     */
    @SuppressLint("Override")
public void setPaddingRelative(int start, int top, int end, int bottom) {
        super.setPadding(start, top, end, bottom);
        updatePolygonSize();
        invalidate();
    }


    /**
     * Take cares about padding changes.
     *
     * @param left   left
     * @param top    top
     * @param right  right
     * @param bottom bottom
     */
    @Override
    public void setPadding(int left, int top, int right, int bottom) {
        super.setPadding(left, top, right, bottom);
        updatePolygonSize(left, top, right, bottom);
        invalidate(left, top, right, bottom);
    }


    /**
     * Update image.
     *
     * @param bm new image.
     */
    @Override
    public void setImageBitmap(Bitmap bm) {
        super.setImageBitmap(bm);
        refreshImage();
        invalidate();
    }


    /**
     * Update image.
     *
     * @param drawable new image.
     */
    @Override
    public void setImageDrawable(Drawable drawable) {
        super.setImageDrawable(drawable);
        refreshImage();
        invalidate();
    }


    /**
     * Update image.
     *
     * @param resId new image.
     */
    @Override
    public void setImageResource(int resId) {
        super.setImageResource(resId);
        refreshImage();
        invalidate();
    }


    /**
     * Update image.
     *
     * @param uri new image.
     */
    @Override
    public void setImageURI(Uri uri) {
        super.setImageURI(uri);
        refreshImage();
        invalidate();
    }


    @Override
    public void setColorFilter(ColorFilter cf) {
        mPaint.setColorFilter(cf);
        invalidate();
    }


    public void setColorFilterWithBorder(ColorFilter cf) {
        mPaint.setColorFilter(cf);
        mBorderPaint.setColorFilter(cf);
        invalidate();
    }


    /**
     * Refresh image with new canvas size or new image.
     */
    @SuppressLint("NewApi")
private void refreshImage() {
        Bitmap image = drawableToBitmap(getDrawable());
        int canvasSize = Math.min(canvasWidth, canvasHeight);
        if (canvasSize > 0 && image != null) {
            //Preserve image ratio if it is not square
            BitmapShader shader = new BitmapShader(ThumbnailUtils.extractThumbnail(image, canvasSize, canvasSize),
                    Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
            mPaint.setShader(shader);
        }
    }


    /**
     * Rebuild polygon after changes, take cares about padding, border and shadow radius.
     * Rotate vertices with the variable angle.
     */
    private void rebuildPolygon() {
        //recalculate new center
        float borderNeeded = mPolygonShapeSpec.hasBorder() ? mPolygonShapeSpec.getBorderWidth() : 0;
        float shadowNeeded = mPolygonShapeSpec.hasShadow() ? mPolygonShapeSpec.getShadowRadius() : 0;
        mPolygonShapeSpec.setCenterX(mPolygonShapeSpec.getDiameter() / 2 + (float) (getPaddingLeft() +
                getPaddingRight()) / 2 + borderNeeded + shadowNeeded);
        mPolygonShapeSpec.setCenterY(mPolygonShapeSpec.getDiameter() / 2 + (float) (getPaddingTop() +
                getPaddingBottom()) / 2 + borderNeeded + shadowNeeded);


        if (mPolygonShapeSpec.getNumVertex() < 3)
            return;


        mPath = mPolygonShape.getPolygonPath(mPolygonShapeSpec);
    }


    /**
     * Update polygon size with unspecified padding.
     */
    private void updatePolygonSize() {
        updatePolygonSize(getPaddingLeft(), getPaddingTop(), getPaddingRight(), getPaddingBottom());
    }


    /**
     * Update polygon with new padding.
     *
     * @param l Left padding.
     * @param t Top padding.
     * @param r Right padding.
     * @param b Bottom padding.
     */
    private void updatePolygonSize(int l, int t, int r, int b) {
        if(mPolygonShapeSpec == null)
            return;


        float borderPadding = mPolygonShapeSpec.hasBorder() ? mPolygonShapeSpec.getBorderWidth() : 0f;
        float shadowPadding = mPolygonShapeSpec.hasShadow() ? mPolygonShapeSpec.getShadowRadius() : 0f;
        float xPadding = (l + r + (borderPadding * 2) + (shadowPadding * 2));
        float yPadding = (t + b + (borderPadding * 2) + (shadowPadding * 2));
        float diameter = Math.min((float) canvasWidth - xPadding, (float) canvasHeight - yPadding);
        //if the size is changed we need to rebuild the polygon
        if (diameter != mPolygonShapeSpec.getDiameter()) {
            mPolygonShapeSpec.setDiameter(diameter);
            rebuildPolygon();
        }
    }


    /**
     *
     * @param polygonShape set new shape
     */
    public void setPolygonShape(PolygonShape polygonShape) {
        mPolygonShape = polygonShape;
        rebuildPolygon();
        invalidate();
    }


    /**
     *
     * @return get current shape
     */
    public PolygonShape getPolygonShape() {
        return mPolygonShape;
    }


    /**
     *
     * @return get current shape spec
     */
    public PolygonShapeSpec getPolygonShapeSpec() {
        return mPolygonShapeSpec;
    }


    /**
     *
     * @param spec set shape spec
     */
    public void setPolygonShapeSpec(PolygonShapeSpec spec) {
        this.mPolygonShapeSpec = spec;
    }


    /**
     * Returns the rotate angle.
     *
     * @return angle in degrees.
     */
    public float getRotationAngle() {
        return mPolygonShapeSpec.getRotation();
    }


    /**
     * Set new rotate angle and updates polygon form.
     *
     * @param mAngle angle in degrees.
     */
    public void setRotationAngle(float mAngle) {
        mPolygonShapeSpec.setRotation(mAngle);
        rebuildPolygon();
        invalidate();
    }


    /**
     * Returns the vertex number.
     *
     * @return vertex number
     */
    public int getVertices() {
        return mPolygonShapeSpec.getNumVertex();
    }


    /**
     * Sets the new vertex number and updates polygon form.
     *
     * @param numVertices new number of vertices
     */
    public void setVertices(int numVertices) {
        mPolygonShapeSpec.setNumVertex(numVertices);
        rebuildPolygon();
        invalidate();
    }


    /**
     * Indicates if it's bordered.
     *
     * @return boolean
     */
    public boolean isBordered() {
        return mPolygonShapeSpec.hasBorder();
    }


    /**
     * Enables or disables the border option.
     *
     * @param bordered if it's bordered
     */
    public void setBorder(boolean bordered) {
        mPolygonShapeSpec.setHasBorder(bordered);
        updateBorderSpecs();
    }


    /**
     * Sets new border width.
     *
     * @param borderWidth new width.
     */
    public void setBorderWidth(float borderWidth) {
        mPolygonShapeSpec.setBorderWidth(borderWidth * (getResources().getDisplayMetrics().density));
        updateBorderSpecs();
    }


    /**
     * Sets new border width and update polygon size.
     */
    private void updateBorderSpecs() {
        if(mPolygonShapeSpec.hasBorder()) {
            mBorderPaint.setStrokeWidth(mPolygonShapeSpec.getBorderWidth());
            mBorderPaint.setColor(mPolygonShapeSpec.getBorderColor());
        } else {
            mBorderPaint.setStrokeWidth(0);
            mBorderPaint.setColor(0);
        }
        updatePolygonSize();
        invalidate();
    }


    /**
     * Sets new border color.
     *
     * @param borderColor Color class var.
     */
    public void setBorderColor(int borderColor) {
        mPolygonShapeSpec.setBorderColor(borderColor);
        mBorderPaint.setColor(borderColor);
        invalidate();
    }


    /**
     * Sets new border color
     *
     * @param resourceBorderColor Resource xml color.
     */
    public void setBorderColorResource(int resourceBorderColor) {
        setBorderColor(getResources().getColor(resourceBorderColor));
    }


    public void addBorder(float borderWidth, int borderColor) {
        mPolygonShapeSpec.setHasBorder(true);
        mPolygonShapeSpec.setBorderWidth(borderWidth * (getResources().getDisplayMetrics().density));
        mPolygonShapeSpec.setBorderColor(borderColor);
        updateBorderSpecs();
    }


    public void addBorderResource(float borderWidth, int resourceBorderColor) {
        addBorder(borderWidth, getResources().getColor(resourceBorderColor));
    }


    /**
     * Sets new radius for corners and updates view.
     *
     * @param cornerRadius new corner radius
     */
    public void setCornerRadius(float cornerRadius) {
        mPolygonShapeSpec.setCornerRadius(cornerRadius);
        mBorderPaint.setPathEffect(new CornerPathEffect(cornerRadius));
        mPaint.setPathEffect(new CornerPathEffect(cornerRadius));
        invalidate();
    }


    /**
     * Adds a default shadow
     */
    public void addShadow() {
        startShadow();
    }


    /**
     * Adds a specific shadow.
     *
     * @param radius  shadow blur size.
     * @param offsetX negative value moves shadow to left and positive to right.
     * @param offsetY negative value moves shadow down and positive towards up.
     * @param color   shadow color
     */
    public void addShadow(float radius, float offsetX, float offsetY, int color) {
        mPolygonShapeSpec.setShadowRadius(radius);
        mPolygonShapeSpec.setShadowXOffset(offsetX);
        mPolygonShapeSpec.setShadowYOffset(offsetY);
        mPolygonShapeSpec.setShadowColor(color);
        startShadow();
    }


    public void addShadowResource(float radius, float offsetX, float offsetY, int color) {
        addShadow(radius, offsetX, offsetY, getResources().getColor(color));
    }


    private void startShadow() {
        mPolygonShapeSpec.setHasShadow(true);
        mBorderPaint.setShadowLayer(mPolygonShapeSpec.getShadowRadius(), mPolygonShapeSpec.getShadowXOffset(),
                mPolygonShapeSpec.getShadowYOffset(), mPolygonShapeSpec.getShadowColor());
        updatePolygonSize();
        invalidate();
    }


    /**
     * Removes shadow
     */
    public void clearShadow() {
        if (!mPolygonShapeSpec.hasShadow())
            return;


        mPolygonShapeSpec.setHasShadow(false);
        mBorderPaint.clearShadowLayer();
        updatePolygonSize();
        invalidate();
    }


    /**
     * Transforms a drawable into a bitmap.
     *
     * @param drawable incoming drawable
     * @return new bitmap
     */
    private static Bitmap drawableToBitmap(Drawable drawable) {
        if (drawable == null) {
            return null;
        } else if (drawable instanceof BitmapDrawable) {
            return ((BitmapDrawable) drawable).getBitmap();
        }


        //Avoid Color Drawable special case
        int width = drawable.getIntrinsicWidth();
        width = width > 0 ? width : 1;
        int height = drawable.getIntrinsicHeight();
        height = height > 0 ? height : 1;


        Bitmap bitmap;
        try {
            bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        } catch (OutOfMemoryError e) {
            Log.e("PolygonImageView", "OutOfMemory during bitmap creation");
            return null;
        }


        Canvas canvas = new Canvas(bitmap);
        drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
        drawable.draw(canvas);


        return bitmap;
    }

}

下面这个是工具类

package net.grobas.view.util;


import java.util.Arrays;
import java.util.Collections;
import java.util.List;


/**
 * Geometry Util
 */
public class GeometryUtil {


    /**
     * Intersection between line, determined for two points, and a circle with radius x
     * @param pointA line point a
     * @param pointB line point b
     * @param center point center circle
     * @param radius circle radius
     * @return intersection points
     */
    public static List<Point> getCircleLineIntersectionPoint(Point pointA, Point pointB, Point center, double radius) {
        double baX = pointB.x - pointA.x;
        double baY = pointB.y - pointA.y;
        double caX = center.x - pointA.x;
        double caY = center.y - pointA.y;


        double a = baX * baX + baY * baY;
        double bBy2 = baX * caX + baY * caY;
        double c = caX * caX + caY * caY - radius * radius;


        double pBy2 = bBy2 / a;
        double q = c / a;


        double disc = pBy2 * pBy2 - q;
        if (disc < 0) {
            return Collections.emptyList();
        }


        double tmpSqrt = Math.sqrt(disc);
        double abScalingFactor1 = -pBy2 + tmpSqrt;
        double abScalingFactor2 = -pBy2 - tmpSqrt;


        Point p1 = new Point(pointA.x - baX * abScalingFactor1, pointA.y
                - baY * abScalingFactor1);
        if (disc == 0) {
            return Collections.singletonList(p1);
        }
        Point p2 = new Point(pointA.x - baX * abScalingFactor2, pointA.y
                - baY * abScalingFactor2);
        return Arrays.asList(p1, p2);
    }


    public static class Point {
        public double x, y;


        public Point(double x, double y) {
            this.x = x;
            this.y = y;
        }




    }
}

下面是使用的方法

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true">


    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:fitsSystemWindows="true">


        <LinearLayout
            android:id="@+id/layout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center_horizontal"
            android:orientation="vertical">


            <net.grobas.view.PolygonImageView
                android:id="@+id/kitty01"
                android:layout_width="250dp"
                android:layout_height="250dp"
                android:src="@drawable/cat07"
                app:poly_border="true"
                app:poly_border_color="@android:color/white"
                app:poly_border_width="5dp"
                app:poly_corner_radius="25"
                app:poly_rotation_angle="0"
                app:poly_shadow="true"
                app:poly_shadow_color="#f01"
                app:poly_vertices="6" />


            <net.grobas.view.PolygonImageView
                android:layout_width="250dp"
                android:layout_height="250dp"
                android:src="@drawable/cat03"
                app:poly_border="true"
                app:poly_border_color="#f01"
                app:poly_border_width="5dp"
                app:poly_corner_radius="5"
                app:poly_rotation_angle="0"
                app:poly_shadow="true"
                app:poly_vertices="0" />



</LinearLayout>
    </ScrollView>
</RelativeLayout>

记得加入配置文件哦

<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2014 Albert Grobas


 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at


      http://www.apache.org/licenses/LICENSE-2.0


 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
-->


<resources>
    <declare-styleable name="PolygonImageView" >
        <attr name="poly_vertices" format="integer" />
        <attr name="poly_rotation_angle" format="float" />
        <attr name="poly_corner_radius" format="float" />
        <attr name="poly_border" format="boolean" />
        <attr name="poly_border_color" format="color" />
        <attr name="poly_border_width" format="dimension" />
        <attr name="poly_shadow" format="boolean" />
        <attr name="poly_shadow_color" format="color" />
    </declare-styleable>


    <declare-styleable name="PolygonImageViewTheme" >
        <attr name="polygonImageViewStyle" format="reference" />
    </declare-styleable>


</resources>

大功告成。可以运行看看哦!

源码地址 :https://download.csdn.net/download/seven2729/8968161

转载请注明:优酷5.01 上拉刷新SwipeRefreshLayout_刷拉颜一seven_seven2729的博客-CSDN博客

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值