SimpleWord 简词开发记录笔记

这篇博客记录了Android应用的开发过程,从导入Excel创建词库,利用SQLite数据库存储,到优化Fragment切换,实现RecyclerView卡片式滑动。还涉及到在ActionBar中添加Spinner下拉列表,处理不同Fragment的Actionbar,以及实现悬浮窗功能,包括状态栏通知、点击外部区域移除悬浮窗等。此外,博客还涵盖了服务Service的启动和关闭、AlarmManager定时任务、BroadcastReceiver以及RecyclerView的各种交互和适配技巧。
摘要由CSDN通过智能技术生成

2015-8-6 15:07:54

词库

找不到词库,暂时找到一个带音标和释义的考研单词excel(估计是好几年前的大纲词汇),就先用这个吧。
excel不能显示音标的话,还得下载字体TOPhonetic.ttf。

数据库

excel导入SQLite
试了几个可视化工具,就SQLiyeStudio比较满意,也没有乱码。
开始时把excel另存为.csv文件,系统的分隔符是逗号,但是去控制面板改了,excel导出时还是逗号,不知道为什么,可能需要重启电脑吧,懒得重启。竟然忘记replace了!所以excel → 制表符分隔的txt → replace分隔符,就ok了^_^

优化——延迟切换fragment

public void switchContent(Fragment fragment) {
    contentFragment = fragment;
    getFragmentManager().beginTransaction().replace(R.id.content_frame, fragment).commit();
//  sm.showContent();
    Handler h = new Handler();
    h.postDelayed(new Runnable() {
        public void run() {
            sm.showContent();
        }
    }, 50);
}

2015-8-10

单词本界面想做成卡片来滑动

RecyclerView

关于引入V7包:http://wp.aesean.com/?p=185

2015-8-11

  1. 继承RecyclerView.Adapter出现The hierarchy of the type ViewHolder is inconsistent,因为菜单用的SlidingMenu,support-v4包可能不是最新的,将SlidingMenu的libs下的v4包替换成最新的就可以了。
  2. Call requires API level 21 (current min is 17): android.content.Context#getDrawable
    解决

    ContextCompat.getDrawable(this, R.drawable.your_drawable)

2015-8-12

在actionbar添加spinner下拉列表

1.actionbar.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:gravity="center_vertical"  
android:orientation="horizontal" >  

    <TextView  
        android:id="@+id/action_bar_title"  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content"          
        android:text="下拉列表" />  

    <!-- 下拉列表  -->  
    <Spinner  
        android:id="@+id/action_bar_spinner"  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content"  
        >  
    </Spinner>  

</LinearLayout>  

2.在activity的onCreate()或fragment的onCreateView()中添加代码,初始化下拉列表数据

//使自定义的普通View能在title栏显示,actionBar.setCustomView能起作用
getActivity().getActionBar().setDisplayShowCustomEnabled(true); 
//初始化下拉列表
View actionbarLayout = view.inflate(activity,R.layout.actionbar, null);  
mActionbarSpinner = (Spinner) actionbarLayout.findViewById(R.id.actionbar_spinner);  

//初始化下拉列表数据
//方法一  
initSpinnerMethod1();  
//方法二  
//initSpinnerMethod2();  

//事件监听  
mActionbarSpinner.setOnItemSelectedListener(new SpinnerItemSelectedListener());  

//在Bar上显示定制view  
actionBar.setCustomView(actionbarLayout); 

初始化下拉列表数据:
(1) 在strings.xml添加数组

<string-array name="spinner_list" >  
<item>item1</item>  
<item>item2</item>  
<item>item3</item>   
</string-array>  

代码:

private void initSpinnerMethod1() {  
     String[] mItems = getResources().getStringArray(R.array.spinner_list);  
     ArrayAdapter<String> spinnerAdapter = new ArrayAdapter<String>(activity,android.R.layout.simple_spinner_item, mItems);  
     spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);  
     mActionbarSpinner.setAdapter(spinnerAdapter);  
}  

(2)在代码中添加数组

private List<String> getData(){          
    List<String> data = new ArrayList<String>();  
    data.add("item1");  
    data.add("item2");  
    data.add("item3");          
    return data;  
}  

private void initSpinnerMethod2()  {  
    mActionbarSpinner.setAdapter(  
            new ArrayAdapter<String>(activity,   
                    android.R.layout.simple_expandable_list_item_1,getData()));    
}  

3.监听action_bar的spinner的item选择事件

private class SpinnerItemSelectedListener implements OnItemSelectedListener {  

    @Override  
    public void onItemSelected(AdapterView<?> arg0, View view, int position,long arg3) {  
         String str= arg0.getItemAtPosition(position).toString();  
         Toast.makeText(MainActivity.this, "你选择的是:"+str, 2000).show();             
    }  

    @Override  
    public void onNothingSelected(AdapterView<?> arg0) {}  
}  

同一个activity的不同fragment显示不同的actionbar

在单词本界面的actionbar想显示一个spinner下拉选择单词本(暂时未加入切换单词本的功能),在WordBookFragment的onCreateView()里设置自定义actionbar

getActivity().getActionBar().setDisplayShowCustomEnabled(true);

别的fragment里设置

getActivity().getActionBar().setDisplayShowCustomEnabled(false);

或者加载另外的view。

不同fragment的标题

分别在onCreateView()里设置

getActivity().setTitle("标题");

若写成

getActivity().getActionBar().setTitle("标题");    

会将所有fragment设置成同一标题

在fragment里获取actionbar

cannot convert from android.support.v7.app.ActionBar to android.app.ActionBar

ActionBar actionBar = getActivity.getActionBar();

使用quick fix:

android.app.ActionBar actionBar;  

2015-8-18

控件左右对齐

使用RelativeLayout

android:layout_alignParentRight="true"

2015-8-19

判断程序是否首次运行

使用SharedPreferences,在onCreate()里:

SharedPreferences preferences;  
Editor editor;  
if (preferences.getBoolean("firststart", true)) {  //获取boolean值,可为缺省值,若缺省,则返回参数二的值
    editor = preferences.edit();  
    editor.putBoolean("firststart", false);  //若是首次,则改为false
    editor.commit();  //提交
}  

2015-8-20

状态栏通知

可删除

NotificationCompat.Builder mBuilder =
    new NotificationCompat.Builder(this)
    .setSmallIcon(R.drawable.notification_icon)
    .setContentTitle("My notification")
    .setContentText("Hello World!");
// Creates an explicit intent for an Activity in your app
Intent resultIntent = new Intent(this, ResultActivity.class);
// The stack builder object will contain an artificial back stack for the
// started Activity.
// This ensures that navigating backward from the Activity leads out of
// your application to the Home screen.
TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
// Adds the back stack for the Intent (but not the Intent itself)
stackBuilder.addParentStack(ResultActivity.class);
// Adds the Intent that starts the Activity to the top of the stack
stackBuilder.addNextIntent(resultIntent);
PendingIntent resultPendingIntent =
        stackBuilder.getPendingIntent(
            0,
            PendingIntent.FLAG_UPDATE_CURRENT
        );
mBuilder.setContentIntent(resultPendingIntent);
NotificationManager mNotificationManager =
    (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
// mId allows you to update the notification later on.
mNotificationManager.notify(mId, mBuilder.build());

常驻状态栏(显示在“进行中/ongoing”)

Notification

  • 低于API 11

    notification.flags |= Notification.FLAG_NO_CLEAR;
    
  • 高于API 11 或者 使用Android Support Library

    //获取状态通知栏管理
    NotificationManager mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); 
    //实例化通知栏构造器
    NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this);  
    mBuilder.setSmallIcon(R.drawable.ic_launcher);//设置通知小图标
        .setContentTitle("标题")  //设置通知栏标题
        .setContentText("内容")   //设置通知栏显示内容
        .setContentIntent(getDefalutIntent(Notification.FLAG_AUTO_CANCEL)) //设置通知栏点击意图
        .setNumber(number) //设置通知集合的数量
        .setTicker("通知来啦") //通知首次出现在通知栏,带上升动画效果的
        .setWhen(System.currentTimeMillis())//通知产生的时间,会在通知信息里显示,一般是系统获取到的时间
        .setPriority(Notification.PRIORITY_DEFAULT) //设置该通知优先级
        .setAutoCancel(true)//设置这个标志当用户单击面板就可以让通知将自动取消  
        .setOngoing(false)//ture,设置他为一个正在进行的通知。他们通常是用来表示一个后台任务,用户积极参与(如播放音乐)或以某种方式正在等待,因此占用设备(如一个文件下载,同步操作,主动网络连接)
        .setDefaults(Notification.DEFAULT_VIBRATE)//向通知添加声音、闪灯和振动效果的最简单、最一致的方式是使用当前的用户默认设置,使用defaults属性,可以组合
    //Notification.DEFAULT_ALL  Notification.DEFAULT_SOUND 添加声音 // requires VIBRATE permission
    

service前台服务

在service里

startForeground(mID, notification);     //设置前台服务

关于服务service

  • 启动service的时候,onCreate方法只有第一次会调用,onStartCommandonStart(已被淘汰)每次都被调用。onStartCommand会告诉系统如何重启服务,如判断是否异常终止后重新启动,在何种情况下异常终止。
  • 2.0 API level之后,实现onStart等同于重写onStartCommand并返回START_STICKY 。
  • 2.0 API level之后,onStart()方法被onStartCommand()取代了。
  • 必须在AndroidManifest.xml中注册

    <service android:name="完整包名.ServiceNotification" />
    

启动服务

mContext.startService(intent);

关闭服务

mContext.stopService(intent);

保存、恢复spinner的选中项

保存

onItemSelected()

int userChoice = spinner.getSelectedItemPosition();
SharedPreferences sharedPref = getSharedPreferences("FileName",0);
SharedPreferences.Editor prefEditor = sharedPref.edit();
prefEditor.putInt("userChoiceSpinner",usersChoice);
prefEditor.commit();

恢复

SharedPreferences sharedPref = getSharedPreferences("FileName",MODE_PRIVATE);
int spinnerValue = sharedPref.getInt"userChoiceSpinner",-1);
if(spinnerValue != -1) 
    spinner.setSelection(spinnerValue);

关于Activity生命周期该做的事情

onCreate()

  • 使用onCreate()初始化你的Activity:创建UI、为类的变量分配引用、绑定数据到控件、创建Service和线程。
  • 为避免快速的创建和销毁对象引发额外的垃圾回收,如果你的应用程序正常创建一套对象,建议它们在onCreate()中创建,因为在Activity的生命周期中它只被调用一次。
  • onCreate()里面尽量少做事情,避免程序启动太久都看不到界面。

onResume()

当从Paused状态恢复activity时,系统会调用onResume()方法。
系统每次调用onResume()时,activity都处于前台,包括第一次创建的时候。所以,应该实现onResume()来初始化那些在onPause方法里面释放掉的组件,并执行那些activity每次进入Resumed state都需要的初始化动作 (例如开始动画与初始化那些只有在获取用户焦点时才需要的组件)

onPause()

  • 不应使用onPause()来保存用户改变的数据 (例如填入表格中的个人信息) 到永久存储(File或者DB)上。仅仅当确认用户期待那些改变能够被自动保存的时候(例如正在撰写邮件草稿),才把那些数据存到永久存储 。
  • 避免在onPause()时执行CPU-intensive 的工作,例如写数据到DB,因为它会导致切换到下一个activity变得缓慢(应该把那些heavy-load的工作放到onStop()去做)。
  • 如果activity实际上是要被Stop,为了切换的顺畅应减少在OnPause()方法里面的工作量。
  • 停止动画或者是其他正在运行的操作,那些都会导致CPU的浪费.
  • 提交在用户离开时期待保存的内容(例如邮件草稿).
  • 释放系统资源,例如broadcast receivers, sensors (比如GPS), 或者是其他任何会影响到电量的资源。

onStop()

  • 当activity调用onStop()方法, activity不再可见,并且应该释放那些不再需要的所有资源。一旦activity停止了,系统会在需要内存空间时摧毁它的实例。极端情况下,系统会直接杀死我们的app进程,并不执行activity的onDestroy()回调方法, 因此我们需要使用onStop()来释放资源,从而避免内存泄漏。
    所以我们应该使用onStop()来执行那些耗时的释放资源的操作,例如往数据库写信息。
    -无论什么原因导致activity停止,系统总是会在onStop()之前调用onPause()方法。

onDestroy()

大多数app并不需要实现这个方法,因为局部类的references会随着activity的销毁而销毁,并且我们的activity应该在onPause()onStop()中执行清除activity资源的操作。然而,如果activity含有在onCreate调用时创建的后台线程,或者是其他有可能导致内存泄漏的资源,则应该在OnDestroy()时进行资源清理,杀死后台线程。

除非程序在onCreate()方法里面就调用了finish()方法,系统通常是在执行了onPause()onStop()之后再调用onDestroy()。在某些情况下,例如我们的activity只是做了一个临时的逻辑跳转的功能,它只是用来决定跳转到哪一个activity,这样的话,需要在onCreate里面调用finish方法,这样系统会直接调用onDestory,跳过生命周期中的其他方法。

与Activity生命周期结合的应用场景

  • 与广播(Broadcast)结合
    onResume注册广播(registerLinstener),在onPause注销广播(unregisterLinstener)。
    例如:做”摇一摇”功能(传感器)、监听网络变化,就可以在onResume中注册监听,在onPause里注销掉,已节省资源提高效率。
  • 与服务(Service)结合
    onStartCommand绑定服务(bindService),在onStop中取消绑定(unbindService)。
    例如:需要通过Service定时更新UI上的数据,而Activity的可见周期在onStartonStop之间,那么就可以再onStart时启动服务,在onStop时停止服务。为了节约系统资源,除了提高用户体验以外,开发人员应尽可能的优化程序。
  • 与Cursor结合
    使用managedQuery让Activity帮你管理Cursor的生命周期,不用自己去close。
  • 释放资源
    可以在onDestory中释放一些资源。比如可以在onDestory时调用MediaPlayer的release。

AlarmManager定时启动Service

private static AlarmManager am;
private static PendingIntent pendingIntent;
/**
 * 使用 AlarmManager 来 定时启动服务
 */
public static void startPendingIntent(Context context) {
    am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
    Intent intent = new Intent(context, MyService.class);//启动示例Service
    pendingIntent = PendingIntent.getService(context, 0, intent, 0);
    long interval = DateUtils.MINUTE_IN_MILLIS * 30;// 30分钟一次
    long firstWake = System.currentTimeMillis() + interval;
    am.setRepeating(AlarmManager.RTC, firstWake, interval, pendingIntent);
}
public static void stopPendingIntent() {
    if (pendingIntent != null) {
        if ( am == null) {
            am = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
        }
        am.cancel(pendingIntent);
        pendingIntent.cancel();
    }
};

参考:android 使用AlarmManager定时启动service

Timer与AlarmManager的区别

通过调用Thread类的start()方法来启动一个线程,这时此线程处于就绪(可运行)状态,但此时并没有运行,它需要CPU时间片。一旦得到CPU时间片,就会执行run()方法。run()的方法体称为线程体,它包含了要执行的这个线程的内容,run()方法运行结束,此线程也随即终止。

Android 平台上常用的定时器主要有两个:

  • Java的Timer
  • Android的AlarmManager

Timer

Java的Timer类可以用来计划需要循环执行的任务。

简单的说,一个Timer内部封装装了“一个Thread”和“一个TimerTask队列”,这个队列按照一定的方式将任务排队处理。封装的ThreadTimer的构造方法调用时被启动,这个Threadrun方法按照条件去循环这个TimerTask队列,然后调用TimerTaskrun方法。

但是,如果CPU进入了休眠状态,那么这个thread将会因为失去CPU时间片而阻塞,从而造成我们需要的定时任务失效。上述定时任务失效的场景分析:假设定时任务的条件是到了时间xx:yy才能执行,但由于cpu休眠造成线程阻塞的关系,当前系统时间超过了这个时间,即便CPU从终端中恢复了,那么由于条件不满足,定时任务在这一次自然就失效了。

解决方案:它需要用WakeLock让CPU保持唤醒状态。但这样会大量消耗手机电量,大大减短手机待机时间。这种方式不能满足我们的需求。

注:TimerTask实现Runnable接口,但它的run方法只是简单的在Timer中封装的Thread中被调用,并未将其放在其它线程中执行。也就是说timer单线程执行的,那么问题来了,为何要这么费劲的封装一个Runnable接口又不进行多线程调用?

AlarmManager

AlarmManager是Android 系统封装的用于管理RTC的模块,RTC(Real Time Clock)是一个独立的硬件时钟,可以在 CPU休眠时正常运行,在预设的时间到达时,通过中断唤醒CPU。这意味着,如果我们用 AlarmManager来定时执行任务,CPU 可以正常的休眠,只有在需要运行任务时醒来一段很短的时间。

参考:Timer与AlarmManager的区别

Switch开关

Switch mSwitch = (Switch)view.findViewById(R.id.setting_switch_notification_word); 
    mSwitch.setOnCheckedChangeListener(new OnCheckedChangeListener(){

        public void onCheckedChanged(CompoundButton buttonView,boolean isChecked) {
            if (isChecked) {    
                // switch on,开启通知栏单词
                intentNotiService = new Intent(mContext, ServiceNotification.class);
                pendingIntentNotiService = PendingIntent.getService(mContext, 0, intentNotiService, 0);
                minute = 1;
                startPendingIntent(pendingIntentNotiService);
            } else {
                //switch off,关闭通知栏单词
                stopPendingIntent(pendingIntentNotiService);
            }
        }
    });

2015-8-21

解决定时启动通知服务时pop up无效

在Service的onStartCommand()里更新每次Notification需要更新的内容,如单词信息。无需改动的信息在onCreate()里初始化。

  • 原代码:

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        mBuilder
            .setContentText(WordsDB.wordClass.toString())   //测试用的单词信息
            .setWhen(System.currentTimeMillis())    //更新的时间
            .setTicker(WordsDB.wordClass.toString());   //在通知栏动画向上弹出
        startForeground(notifyID, mBuilder.build());    //display in "ongoing"
        Log.d("通知栏单词", WordsDB.wordClass.toString());
        return super.onStartCommand(intent, flags, startId);
    }
    

    按钮点击时会更新notification,也会在通知栏弹出提示。
    但使用AlarmManager定时启动该service时,会更新内容,但不会在通知栏弹出提示,只能自己打开通知栏才能查看到更新。

  • 改动:使用NotificationManager.notify()

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
舍得制作的“语音版剑桥商务英语证书考试”词库For SuperMemo 2006版,直接新建一个SuperMemo词库,导入后即可使用。该词汇表是从新东方背单词5.0版中导出的,本词库利用外挂的语音库可以实现自动语音功能,具体方法如下: 1.下载外挂语音库,下载地址及更详尽的使用方法请看这里:http://blog.sina.com.cn/s/blog_5f2d67f90100d97d.html 2.在D盘下新建一个“Speech”文件夹,将下载好语音库解压到这个文件夹中。如果你不想放到D盘,请修改“雅思词汇导入.txt"文件,将”file:///d:/“中的”d“替换成你想要的其他盘符。(包括文件夹也可以改,但你得熟悉这些基本的电脑操作)。 3.本压缩包中带有音标字体TOPhonetic.ttf。如果音标不能正常显示,需要安装该字体:1)打开控制面板-字体;2)打开菜单"文件->安装新字体";3)选择TOPhonetic.ttf存放的文件夹,安装该字体。   附:词库导入方法:   1.先新建一个库,点击“File->New collection”:   2.检查一下文件保存的位置,一般建议放在默认的“SuperMemo2006”的“Systems”文件夹下(当然放到其他地方也没关系)。在弹出的对话框里填上一个好记的文件名,点击“OK”后继续,这样一个空库就建好了。   3.接下来开始导入,点击菜单“File->Import->Q&A txt”。(如果你的“File”菜单里没有“Import”这一项,那你需要到“File->Level”下打开“Professional”这一级别,“Import”菜单项就会出现了。   然后打开刚才解压出来的词库文件(文本格式),在出现的对话框中,默认选项不用修改,点击OK继续。(Tips:如果这时发现选择的文件不对,可以点击对话框中的“choose file to import”按钮返回重选。)   系统开始转换词库,对话框中的“time expected”就是系统预估所需的转换时间,象我导入一个500多K大的词库文件,大约需要1分20多秒的样子。   建好后,系统自动返回主界面。然后我们就可以点击“File->Open Collection”,   选择我们刚才建好的词库,点击界面左下角的“Learn ”按钮,开始我们的学习吧。    详尽的词库导入图文教程请打开下面这个链接进行阅览:  http://blog.sina.com.cn/s/blog_5f2d67f90100d07a.html 
一、介绍 Simple.Docx是一个简单的word组件,可以对word97以后的docx文件进行书签的替换操作。使用于需要根据模板来生成word文档的客户使用。 (一)程序的优点: 1、小巧。程序大小仅8K; 2、原生,不依赖第三方组件。程序纯C#所写,不依赖任何第三方组件,不需要安装任何word及其他程序; 3、保持文档原有格式。你的word模板设计成什么样式,输出的docx文件就是什么样式,不需要做任何操作,包括字体、颜色等等都按原样输出; 4、速度快。输出速度极快,远快于通过office组件的输出速度; 5、及其简单的调用方式。详见使用说明及例程; 6、可自由设定数据的输出格式。 (二)缺点 1、只能完成书签替换的方式。如果你有需求,需要其他功能,可以Email给我,说不定哪天就添加上了,Email:278544343@QQ.COM 2、支持.net framework 4.5以上。主要是用到了4.5的一个类库。如果需要支持低版本的,可以联系我,使用第三方的类库,可以支持到framework 2.0以上。 (三)Simple系列组件 1、准备做Simple系列组件,通过最简单、简洁的方式,完成需要的功能。还没想好要写什么,这是第一个,还有一个针对金税盘的,其他的需求大家可以给我Email。 2、中年油腻男,懒癌晚期,唯爱好编程。有合适的工作机会,望介绍下,感激不尽。 二、使用方法 (一)准备word模板 1、按照你要生成的word文件使用word等软件设计word; 2、在需要插入数据的地方,插入一个书签,书签名不能重复,最好跟数据类的对应项相同,方便后续调用。 (二)程序调用 1、添加引用:Simple.Docx.dll 2、在程序中 using Simple.Docx; 3、程序调用方法: a、方法1: // 需要提前设计数据类,类的属性要跟书签一致,此处略,详见示例文档 // 类中可以使用AttributeFormat特性来设定数据的输出格式,格式详见String.Format中的format函数。实际上,此组件就是使用String.Format函数来输出指定格式的。详见示例文档 Person tPerson=Person.GetDefault(); // 生成测试对象 string fileName = System.Environment.CurrentDirectory + @"\test.docx"; // docx模板 SimpleDocx myDocx = new SimpleDocx(fileName); myDocx.ReplaceBookMark(tPerson); // 使用对象数据来替换word中的书签(注1) myDocx.SaveDoc(@"d:\111.docx"); // 保存文档 myDocx.Dispose(); // 清理环境垃圾 // 注1:ReplaceBookMark提供两种替换方式,一种是通过类的相同属性名自动替换,如上例所示;还有一种方式是通过dictionary来显式调用,如下例所示 b、方法2: Dictionary<string, string> map = new Dictionary<string, string>(){ {"name","黄大勇"},{"sex","男"},{"birth","1974-12-13"},{"tel","13366666666"},{"email","278544343@QQ.com"} }; string fileName = System.Environment.CurrentDirectory + @"\test.docx"; // docx模板 SimpleDocx myDocx = new SimpleDocx(fileName); myDocx.ReplaceBookMark(map); // 使用对象数据来替换word中的书签 myDocx.SaveDoc(@"d:\111.docx"); // 保存文档 myDocx.Dispose(); // 清理环境垃圾
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值