android画布原理,Android截屏方案实现原理解析

Android截屏的原理:获取具体需要截屏的区域的Bitmap,然后绘制在画布上,保存为图片后进行分享或者其它用途

在截屏功能中,有时需要截取全屏的内容,有时需要截取超过一屏的内容(比如:Listview,Scrollview,RecyclerView)。下面介绍各种场景获取Bitmap的方法

普通截屏的实现

获取当前Window的DrawingCache的方式,即decorView的DrawingCache

/**

* shot the current screen ,with the status but the status is trans *

*

* @param ctx current activity

*/

public static Bitmap shotActivity(Activity ctx) {

View view = ctx.getWindow().getDecorView();

view.setDrawingCacheEnabled(true);

view.buildDrawingCache();

Bitmap bp = Bitmap.createBitmap(view.getDrawingCache(), 0, 0, view.getMeasuredWidth(),

view.getMeasuredHeight());

view.setDrawingCacheEnabled(false);

view.destroyDrawingCache();

return bp;

}

获取当前View的DrawingCache

public static Bitmap getViewBp(View v) {

if (null == v) {

return null;

}

v.setDrawingCacheEnabled(true);

v.buildDrawingCache();

if (Build.VERSION.SDK_INT >= 11) {

v.measure(MeasureSpec.makeMeasureSpec(v.getWidth(),

MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(

v.getHeight(), MeasureSpec.EXACTLY));

v.layout((int) v.getX(), (int) v.getY(),

(int) v.getX() + v.getMeasuredWidth(),

(int) v.getY() + v.getMeasuredHeight());

} else {

v.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),

MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));

v.layout(0, 0, v.getMeasuredWidth(), v.getMeasuredHeight());

}

Bitmap b = Bitmap.createBitmap(v.getDrawingCache(), 0, 0, v.getMeasuredWidth(), v.getMeasuredHeight());

v.setDrawingCacheEnabled(false);

v.destroyDrawingCache();

return b;

}

开源方案

在滚动视图中,如果当前View并没有在视图中全部绘制出来,我们可以利用View的ScrollTo()和ScrollBy()方法来移动画布,同时获取当前View的可视部分的DrawingCache,最后进行拼接得到其Bitmap,参考: PGSSoft/scrollscreenshot@[Github] 。

Scrollview截屏

三个截屏中,ScrollView最简单,因为ScrollView只有一个childView,虽然没有全部显示在界面上,但是已经全部渲染绘制,因此可以直接 调用 scrollView.draw(canvas) 来完成截图

public static Bitmap shotScrollView(ScrollView scrollView) {

int h = 0;

Bitmap bitmap = null;

for (int i = 0; i < scrollView.getChildCount(); i++) {

h += scrollView.getChildAt(i).getHeight();

scrollView.getChildAt(i).setBackgroundColor(Color.parseColor("#ffffff"));

}

bitmap = Bitmap.createBitmap(scrollView.getWidth(), h, Bitmap.Config.RGB_565);

final Canvas canvas = new Canvas(bitmap);

scrollView.draw(canvas);

return bitmap;

}

Scrollview截屏

而ListView就是会回收与重用Item,并且只会绘制在屏幕上显示的ItemView,根据stackoverflow上大神的建议,采用一个List来存储Item的视图,这种方案依然不够好,当Item足够多的时候,可能会发生oom。

public static Bitmap shotListView(ListView listview) {

ListAdapter adapter = listview.getAdapter();

int itemscount = adapter.getCount();

int allitemsheight = 0;

List bmps = new ArrayList();

for (int i = 0; i < itemscount; i++) {

View childView = adapter.getView(i, null, listview);

childView.measure(

View.MeasureSpec.makeMeasureSpec(listview.getWidth(), View.MeasureSpec.EXACTLY),

View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));

childView.layout(0, 0, childView.getMeasuredWidth(), childView.getMeasuredHeight());

childView.setDrawingCacheEnabled(true);

childView.buildDrawingCache();

bmps.add(childView.getDrawingCache());

allitemsheight += childView.getMeasuredHeight();

}

Bitmap bigbitmap =

Bitmap.createBitmap(listview.getMeasuredWidth(), allitemsheight, Bitmap.Config.ARGB_8888);

Canvas bigcanvas = new Canvas(bigbitmap);

Paint paint = new Paint();

int iHeight = 0;

for (int i = 0; i < bmps.size(); i++) {

Bitmap bmp = bmps.get(i);

bigcanvas.drawBitmap(bmp, 0, iHeight, paint);

iHeight += bmp.getHeight();

bmp.recycle();

bmp = null;

}

return bigbitmap;

}

RecyclerView截屏

我们都知道,在新的Android版本中,已经可以用RecyclerView来代替使用ListView的场景,相比较ListView,RecyclerView对Item View的缓存支持的更好。可以采用和ListView相同的方案,这里也是在stackoverflow上看到的方案。

public static Bitmap shotRecyclerView(RecyclerView view) {

RecyclerView.Adapter adapter = view.getAdapter();

Bitmap bigBitmap = null;

if (adapter != null) {

int size = adapter.getItemCount();

int height = 0;

Paint paint = new Paint();

int iHeight = 0;

final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);

// Use 1/8th of the available memory for this memory cache.

final int cacheSize = maxMemory / 8;

LruCache bitmaCache = new LruCache<>(cacheSize);

for (int i = 0; i < size; i++) {

RecyclerView.ViewHolder holder = adapter.createViewHolder(view, adapter.getItemViewType(i));

adapter.onBindViewHolder(holder, i);

holder.itemView.measure(

View.MeasureSpec.makeMeasureSpec(view.getWidth(), View.MeasureSpec.EXACTLY),

View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));

holder.itemView.layout(0, 0, holder.itemView.getMeasuredWidth(),

holder.itemView.getMeasuredHeight());

holder.itemView.setDrawingCacheEnabled(true);

holder.itemView.buildDrawingCache();

Bitmap drawingCache = holder.itemView.getDrawingCache();

if (drawingCache != null) {

bitmaCache.put(String.valueOf(i), drawingCache);

}

height += holder.itemView.getMeasuredHeight();

}

bigBitmap = Bitmap.createBitmap(view.getMeasuredWidth(), height, Bitmap.Config.ARGB_8888);

Canvas bigCanvas = new Canvas(bigBitmap);

Drawable lBackground = view.getBackground();

if (lBackground instanceof ColorDrawable) {

ColorDrawable lColorDrawable = (ColorDrawable) lBackground;

int lColor = lColorDrawable.getColor();

bigCanvas.drawColor(lColor);

}

for (int i = 0; i < size; i++) {

Bitmap bitmap = bitmaCache.get(String.valueOf(i));

bigCanvas.drawBitmap(bitmap, 0f, iHeight, paint);

iHeight += bitmap.getHeight();

bitmap.recycle();

}

}

return bigBitmap;

}

上面的方法在截取存在异步加载图片的RecyclerView时候会出现加载不出图片的情况,这里再补充一种滚动式截屏的方法

public static void screenShotRecycleView(final RecyclerView mRecyclerView, final

RecycleViewRecCallback callBack) {

if (mRecyclerView == null) {

return;

}

BaseListFragment.MyAdapter adapter = (BaseListFragment.MyAdapter) mRecyclerView.getAdapter();

final Paint paint = new Paint();

final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);

// Use 1/8th of the available memory for this memory cache.

final int cacheSize = maxMemory / 8;

LruCache bitmaCache = new LruCache<>(cacheSize);

final int oneScreenHeight = mRecyclerView.getMeasuredHeight();

int shotHeight = 0;

if (adapter != null && adapter.getData().size() > 0) {

int headerSize = adapter.getHeaderLayoutCount();

int dataSize = adapter.getData().size();

for (int i = 0; i < headerSize + dataSize; i++) {

BaseViewHolder holder = (BaseViewHolder) adapter.createViewHolder(mRecyclerView, adapter.getItemViewType(i));

if (i >= headerSize)

adapter.startConvert(holder, adapter.getData().get(i - headerSize));

holder.itemView.measure(

View.MeasureSpec.makeMeasureSpec(mRecyclerView.getWidth(), View.MeasureSpec.EXACTLY),

View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));

holder.itemView.layout(0, 0, holder.itemView.getMeasuredWidth(), holder.itemView.getMeasuredHeight());

holder.itemView.setDrawingCacheEnabled(true);

holder.itemView.buildDrawingCache();

Bitmap drawingCache = holder.itemView.getDrawingCache();

//holder.itemView.destroyDrawingCache();//释放缓存占用的资源

if (drawingCache != null) {

bitmaCache.put(String.valueOf(i), drawingCache);

}

shotHeight += holder.itemView.getHeight();

if (shotHeight > 12000) {

//设置截图最大值

if (callBack != null)

callBack.onRecFinished(null);

return;

}

}

//添加底部高度(加载更多或loading布局高度,此处为固定值:)

final int footHight = Util.dip2px(mRecyclerView.getContext(), 42);

shotHeight += footHight;

//返回到顶部

while (mRecyclerView.canScrollVertically(-1)) {

mRecyclerView.scrollBy(0, -oneScreenHeight);

}

//绘制截图的背景

final Bitmap bigBitmap = Bitmap.createBitmap(mRecyclerView.getMeasuredWidth(), shotHeight, Bitmap.Config.ARGB_8888);

final Canvas bigCanvas = new Canvas(bigBitmap);

Drawable lBackground = mRecyclerView.getBackground();

if (lBackground instanceof ColorDrawable) {

ColorDrawable lColorDrawable = (ColorDrawable) lBackground;

int lColor = lColorDrawable.getColor();

bigCanvas.drawColor(lColor);

}

final int[] drawOffset = {0};

final Canvas canvas = new Canvas();

if (shotHeight <= oneScreenHeight) {

//仅有一页

Bitmap bitmap = Bitmap.createBitmap(mRecyclerView.getWidth(), mRecyclerView.getHeight(), Bitmap.Config.ARGB_8888);

canvas.setBitmap(bitmap);

mRecyclerView.draw(canvas);

if (callBack != null)

callBack.onRecFinished(bitmap);

} else {

//超过一页

final int finalShotHeight = shotHeight;

mRecyclerView.postDelayed(new Runnable() {

@Override

public void run() {

if ((drawOffset[0] + oneScreenHeight < finalShotHeight)) {

//超过一屏

Bitmap bitmap = Bitmap.createBitmap(mRecyclerView.getWidth(), mRecyclerView.getHeight(), Bitmap.Config.ARGB_8888);

canvas.setBitmap(bitmap);

mRecyclerView.draw(canvas);

bigCanvas.drawBitmap(bitmap, 0, drawOffset[0], paint);

drawOffset[0] += oneScreenHeight;

mRecyclerView.scrollBy(0, oneScreenHeight);

try {

bitmap.recycle();

} catch (Exception ex) {

ex.printStackTrace();

}

mRecyclerView.postDelayed(this, 10);

} else {

//不足一屏时的处理

int leftHeight = finalShotHeight - drawOffset[0] - footHight;

mRecyclerView.scrollBy(0, leftHeight);

int top = oneScreenHeight - (finalShotHeight - drawOffset[0]);

if (top > 0 && leftHeight > 0) {

Bitmap bitmap = Bitmap.createBitmap(mRecyclerView.getWidth(), mRecyclerView.getHeight(), Bitmap.Config.ARGB_8888);

canvas.setBitmap(bitmap);

mRecyclerView.draw(canvas);

//截图,只要补足的那块图

bitmap = Bitmap.createBitmap(bitmap, 0, top, bitmap.getWidth(), leftHeight, null, false);

bigCanvas.drawBitmap(bitmap, 0, drawOffset[0], paint);

try {

bitmap.recycle();

} catch (Exception ex) {

ex.printStackTrace();

}

}

if (callBack != null)

callBack.onRecFinished(bigBitmap);

}

}

}, 10);

}

}

}

public interface RecycleViewRecCallback {

void onRecFinished(Bitmap bitmap);

}

相信有不少小伙伴用BRVH第三方库来做recycleview的适配器的。使用这个库的话再用上面的方法会报角标越界的错误,看了BRVH的源码

public void onBindViewHolder(ViewHolder holder, int positions) {

int viewType = holder.getItemViewType();

switch(viewType) {

case 0:

this.convert((BaseViewHolder)holder, this.mData.get(holder.getLayoutPosition() - this.getHeaderLayoutCount()));

case 273:

case 819:

case 1365:

break;

case 546:

this.addLoadMore(holder);

break;

default:

this.convert((BaseViewHolder)holder, this.mData.get(holder.getLayoutPosition() - this.getHeaderLayoutCount()));

this.onBindDefViewHolder((BaseViewHolder)holder, this.mData.get(holder.getLayoutPosition() - this.getHeaderLayoutCount()));

}

}

在调用 adapter.onBindViewHolder 时,因为里面的 position 参数未使用,里面用的计算 holder.getLayoutPosition() - this.getHeaderLayoutCount() 的值一直是-1导致角标越界报错。

本人理解,RecyclerView的截屏原理是,首先构造每个item的ViewHolder,然后调用具体设置数据到每个item的方法,此时cache中就存有item的内容,此时绘制就能获取到完整的内容。采用v7包中的 onBindViewHolder 方法即可,或者是BRVH的 convert 方法,可以看到BRVH中没有暴露出这个方法,而且唯一暴露出的 onBindViewHolder 还会报角标越界错误,此时我们就需要在BRVH的基础上暴露出 convert 即可,代码如下

public class MyAdapter extends BaseQuickAdapter {

public MyAdapter() {

super(getItemLayoutResId(), datas);

}

/**

* 用于对外暴露convert方法,构造缓存视图(截屏用)

* @param viewHolder

* @param t

*/

public void startConvert(BaseViewHolder viewHolder, T t){

convert(viewHolder,t);

}

@Override

protected void convert(BaseViewHolder viewHolder, T t) {

bindView(viewHolder, t);

}

}

然后将上面所述的获取Bitmap方法修改一下

/**

* 截取recycler view

*/

public static Bitmap getRecyclerViewScreenshot(RecyclerView view) {

BaseListFragment.MyAdapter adapter = (BaseListFragment.MyAdapter) view.getAdapter();

Bitmap bigBitmap = null;

if (adapter != null) {

int size = adapter.getData().size();

int height = 0;

Paint paint = new Paint();

int iHeight = 0;

final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);

// Use 1/8th of the available memory for this memory cache.

final int cacheSize = maxMemory / 8;

LruCache bitmaCache = new LruCache<>(cacheSize);

for (int i = 0; i < size; i++) {

BaseViewHolder holder = (BaseViewHolder) adapter.createViewHolder(view, adapter.getItemViewType(i));

//此处需要调用convert方法,否则绘制出来的都是空的item

adapter.startConvert(holder, adapter.getData().get(i));

holder.itemView.measure(

View.MeasureSpec.makeMeasureSpec(view.getWidth(), View.MeasureSpec.EXACTLY),

View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));

holder.itemView.layout(0, 0, holder.itemView.getMeasuredWidth(),

holder.itemView.getMeasuredHeight());

holder.itemView.setDrawingCacheEnabled(true);

holder.itemView.buildDrawingCache();

Bitmap drawingCache = holder.itemView.getDrawingCache();

if (drawingCache != null) {

bitmaCache.put(String.valueOf(i), drawingCache);

}

height += holder.itemView.getMeasuredHeight();

}

bigBitmap = Bitmap.createBitmap(view.getMeasuredWidth(), height, Bitmap.Config.ARGB_8888);

Canvas bigCanvas = new Canvas(bigBitmap);

Drawable lBackground = view.getBackground();

if (lBackground instanceof ColorDrawable) {

ColorDrawable lColorDrawable = (ColorDrawable) lBackground;

int lColor = lColorDrawable.getColor();

bigCanvas.drawColor(lColor);

}

for (int i = 0; i < size; i++) {

Bitmap bitmap = bitmaCache.get(String.valueOf(i));

bigCanvas.drawBitmap(bitmap, 0f, iHeight, paint);

iHeight += bitmap.getHeight();

bitmap.recycle();

}

}

return bigBitmap;

}

合成Bitmap

比如四张合成一张

/**

* 将四张图拼成一张

*

* @param pic1 图一

* @param pic2 图二

* @param pic3 图三

* @param pic4 图四

* @return only_bitmap

* 详情见说明:{@link com.bertadata.qxb.util.ScreenShotUtils}

*/

public static Bitmap combineBitmapsIntoOnlyOne(Bitmap pic1, Bitmap pic2, Bitmap pic3, Bitmap pic4, Activity context) {

int w_total = pic2.getWidth();

int h_total = pic1.getHeight() + pic2.getHeight() + pic3.getHeight() + pic4.getHeight();

int h_pic1 = pic1.getHeight();

int h_pic4 = pic4.getHeight();

int h_pic12 = pic1.getHeight() + pic2.getHeight();

//此处为防止OOM需要对高度做限制

if (h_total > HEIGHTLIMIT) {

return null;

}

Bitmap only_bitmap = Bitmap.createBitmap(w_total, h_total, Bitmap.Config.ARGB_4444);

Canvas canvas = new Canvas(only_bitmap);

canvas.drawColor(ContextCompat.getColor(context, R.color.color_content_bg));

canvas.drawBitmap(pic1, 0, 0, null);

canvas.drawBitmap(pic2, 0, h_pic1, null);

canvas.drawBitmap(pic3, 0, h_pic12, null);

canvas.drawBitmap(pic4, 0, h_total - h_pic4, null);

return only_bitmap;

}

图片后期处理

/**

* 将传入的Bitmap合理压缩后输出到系统截屏目录下

* 命名格式为:Screenshot+时间戳+启信宝报名.jpg

* 同时通知系统重新扫描系统文件

*

* @param pic1 图一 标题栏截图

* @param pic2 图二 scrollview截图

* @param context 用于通知重新扫描文件系统,为提升性能可去掉

* 详情见说明:{@link com.bertadata.qxb.util.ScreenShotUtils}

*/

public static void savingBitmapIntoFile(final Bitmap pic1, final Bitmap pic2, final Activity context, final BitmapAndFileCallBack callBack) {

if (context == null || context.isFinishing()) {

return;

}

Thread thread = new Thread(new Runnable() {

@Override

public void run() {

String fileReturnPath = "";

int w = pic1.getWidth();

Bitmap bottom = BitmapFactory.decodeResource(context.getResources(), R.drawable.ic_picture_combine_bottom);

Bitmap top_banner = BitmapFactory.decodeResource(context.getResources(), R.drawable.ic_picture_combine_top);

Bitmap bitmap_bottom = anyRatioCompressing(bottom, (float) w / bottom.getWidth(), (float) w / bottom.getWidth());

Bitmap bitmap_top = anyRatioCompressing(top_banner, (float) w / bottom.getWidth(), (float) w / bottom.getWidth());

final Bitmap only_bitmap = combineBitmapsIntoOnlyOne(bitmap_top, pic1, pic2, bitmap_bottom, context);

// 获取当前时间

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss-ms", Locale.getDefault());

String data = sdf.format(new Date());

// 获取内存路径

// 设置图片路径+命名规范

// 声明输出文件

String storagePath = Environment.getExternalStorageDirectory().getAbsolutePath();

String fileTitle = "Screenshot_" + data + "_com.bertadata.qxb.biz_info.jpg";

String filePath = storagePath + "/DCIM/";

final String fileAbsolutePath = filePath + fileTitle;

File file = new File(fileAbsolutePath);

/**

* 质压与比压结合

* 分级压缩

* 输出文件

*/

if (only_bitmap != null) {

try {

// 首先,对原图进行一步质量压缩,形成初步文件

FileOutputStream fos = new FileOutputStream(file);

only_bitmap.compress(Bitmap.CompressFormat.JPEG, 50, fos);

// 另建一个文件other_file预备输出

String other_fileTitle = "Screenshot_" + data + "_com.bertadata.qxb.jpg";

String other_fileAbsolutePath = filePath + other_fileTitle;

File other_file = new File(other_fileAbsolutePath);

FileOutputStream other_fos = new FileOutputStream(other_file);

// 其次,要判断质压之后的文件大小,按文件大小分级进行处理

long file_size = file.length() / 1024; // size of file(KB)

if (file_size < 0 || !(file.exists())) {

// 零级: 文件判空

throw new NullPointerException();

} else if (file_size > 0 && file_size <= 256) {

// 一级: 直接输出

deleteFile(other_file);

// 通知刷新文件系统,显示最新截取的图文件

fileReturnPath = fileAbsolutePath;

context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.parse("file://" + fileAbsolutePath)));

} else if (file_size > 256 && file_size <= 768) {

// 二级: 简单压缩:压缩为原比例的3/4,质压为50%

anyRatioCompressing(only_bitmap, (float) 3 / 4, (float) 3 / 4).compress(Bitmap.CompressFormat.JPEG, 40, other_fos);

deleteFile(file);

// 通知刷新文件系统,显示最新截取的图文件

fileReturnPath = other_fileAbsolutePath;

context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.parse("file://" + other_fileAbsolutePath)));

} else if (file_size > 768 && file_size <= 1280) {

// 三级: 中度压缩:压缩为原比例的1/2,质压为40%

anyRatioCompressing(only_bitmap, (float) 1 / 2, (float) 1 / 2).compress(Bitmap.CompressFormat.JPEG, 40, other_fos);

deleteFile(file);

// 通知刷新文件系统,显示最新截取的图文件

fileReturnPath = other_fileAbsolutePath;

context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.parse("file://" + other_fileAbsolutePath)));

} else if (file_size > 1280 && file_size <= 2048) {

// 四级: 大幅压缩:压缩为原比例的1/3,质压为40%

anyRatioCompressing(only_bitmap, (float) 1 / 3, (float) 1 / 3).compress(Bitmap.CompressFormat.JPEG, 40, other_fos);

deleteFile(file);

// 通知刷新文件系统,显示最新截取的图文件

fileReturnPath = other_fileAbsolutePath;

context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.parse("file://" + other_fileAbsolutePath)));

} else if (file_size > 2048) {

// 五级: 中度压缩:压缩为原比例的1/2,质压为40%

anyRatioCompressing(only_bitmap, (float) 1 / 2, (float) 1 / 2).compress(Bitmap.CompressFormat.JPEG, 40, other_fos);

deleteFile(file);

// 通知刷新文件系统,显示最新截取的图文件

fileReturnPath = other_fileAbsolutePath;

context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.parse("file://" + other_fileAbsolutePath)));

}

// 注销fos;

fos.flush();

other_fos.flush();

other_fos.close();

fos.close();

//callback用于回传保存成功的路径以及Bitmap

callBack.onSuccess(only_bitmap, fileReturnPath);

} catch (Exception e) {

e.printStackTrace();

}

} else callBack.onSuccess(null, "");

}

});

thread.start();

}

/**

* 可实现任意宽高比例压缩(宽高压比可不同)的压缩方法(主要用于微压)

*

* @param bitmap 源图

* @param width_ratio 宽压比(float)(0

* @param height_ratio 高压比(float)(0

* @return 目标图片

*

*/

public static Bitmap anyRatioCompressing(Bitmap bitmap, float width_ratio, float height_ratio) {

Matrix matrix = new Matrix();

matrix.postScale(width_ratio, height_ratio);

return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, false);

}

总结

以上所述是小编给大家介绍的Android截屏方案实现原理解析,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对脚本之家网站的支持!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值