Android-Room,RecyclerView,CardView的简单使用以及数据迁移

Room

介绍和依赖

官网地址: https://developer.android.google.cn/jetpack/androidx/releases/room

def room_version = "2.2.5"

    implementation "androidx.room:room-runtime:$room_version"
    annotationProcessor "androidx.room:room-compiler:$room_version"

//    // optional - RxJava support for Room
//    implementation "androidx.room:room-rxjava2:$room_version"
//
//    // optional - Guava support for Room, including Optional and ListenableFuture
//    implementation "androidx.room:room-guava:$room_version"

    // optional - Test helpers
    testImplementation "androidx.room:room-testing:$room_version"

简单案列

  1. 导入依赖

    def room_version = "2.2.5"
    
    implementation "androidx.room:room-runtime:$room_version"
    annotationProcessor "androidx.room:room-compiler:$room_version"
    testImplementation "androidx.room:room-testing:$room_version"
    
  2. 编写实体类

    package com.example.roombasic;
    
    import androidx.room.ColumnInfo;
    import androidx.room.Entity;
    import androidx.room.PrimaryKey;
    
    /**
     * @author codekiller
     * @date 2020/11/16 17:42
     * @Description TODO
     */
    @Entity
    public class Word {
        @PrimaryKey(autoGenerate = true)
        private int id;
    
        @ColumnInfo(name = "english_word")
        private String word;
    
        @ColumnInfo(name = "chinese_meaning")
        private String chineseMeaning;
    
        public Word(){}
    
        public Word(String word, String chineseMeaning) {
            this.word = word;
            this.chineseMeaning = chineseMeaning;
        }
    
        public int getId() {
            return id;
        }
    
        public void setId(int id) {
            this.id = id;
        }
    
        public Word setIdc(int id){
            this.id = id;
            return this;
        }
    
        public String getWord() {
            return word;
        }
    
        public void setWord(String word) {
            this.word = word;
        }
    
        public String getChineseMeaning() {
            return chineseMeaning;
        }
    
        public void setChineseMeaning(String chineseMeaning) {
            this.chineseMeaning = chineseMeaning;
        }
    }
    
  3. 编写数据库操作Dao类

    package com.example.roombasic;
    
    import androidx.room.Dao;
    import androidx.room.Delete;
    import androidx.room.Insert;
    import androidx.room.Query;
    import androidx.room.Update;
    
    import java.util.List;
    
    /**
     * @author codekiller
     * @date 2020/11/16 17:44
     * @Description Dao: Database Access Object
     */
    @Dao
    public interface WordDao {
    
        @Insert
        void insertWords(Word... words);
    
        @Update
        void updateWords(Word... words);
    
        @Delete
        void deleteWords(Word... words);
    
        @Query("DELETE FROM WORD")
        void deleteAllWords();
    
        @Query("SELECT * FROM WORD ORDER BY ID DESC")
        List<Word> getAllWords();
    }
    
  4. 数据库类

    @Database(entities = {Word.class}, version = 1, exportSchema = false)
    public abstract class WordDatabase extends RoomDatabase {
    
        public abstract  WordDao getWordDao();
    }
    
  5. 界面

  6. 具体的数据库操作

    package com.example.roombasic;
    
    import androidx.appcompat.app.AppCompatActivity;
    import androidx.room.Room;
    import androidx.room.util.StringUtil;
    
    import android.os.Bundle;
    import android.widget.Button;
    import android.widget.TextView;
    
    import java.util.List;
    
    public class MainActivity extends AppCompatActivity {
    
        WordDatabase wordDatabase;
        WordDao wordDao;
    
        TextView textView;
        Button insertButton,updateButton,clearButton,deleteButton;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            wordDatabase = Room.databaseBuilder(this,WordDatabase.class,"word_database")
                .allowMainThreadQueries()
                .build();
    
            textView = findViewById(R.id.textView2);
            insertButton = findViewById(R.id.insertButton);
            updateButton = findViewById(R.id.updateButton);
            clearButton = findViewById(R.id.clearButton);
            deleteButton = findViewById(R.id.deleteButton);
    
            wordDao = wordDatabase.getWordDao();
    
            updateView();
    
            //插入操作
            insertButton.setOnClickListener(v->{
                Word word1 = new Word("Hello","你好");
                Word word2 = new Word("World","世界");
                wordDao.insertWords(word1,word2);
                updateView();
            });
    
            //更新操作
            updateButton.setOnClickListener(v->{
                Word word = new Word("spring","春天");
                word.setId(1);
                wordDao.updateWords(word);
                updateView();
            });
    
            //清除操作
            clearButton.setOnClickListener(v->{
                wordDao.deleteAllWords();
                updateView();
            });
    
            //删除操作
            deleteButton.setOnClickListener(v->{
                wordDao.deleteWords(new Word().setIdc(4));
                updateView();
            });
    
    
    
        }
    
        void updateView(){
            List<Word> words = wordDao.getAllWords();
            String text = "";
            for (int i=words.size()-1;i>=0;--i) {
                text += words.get(i).getId()+":"+words.get(i).getWord()+" = "+words.get(i).getChineseMeaning()+";\n";
            }
            textView.setText(text);
    
        }
    
    }
    
  7. 运行相关操作可以在我们的文件中看到


代码优化

在上面的例子中,我们可以看到,数据库的操作都是放在主线程里面进行执行的,这显然不是太好。而且我们的mainActivity里面代码太多了,现在对代码进行一些修改。

看下文件结构

  1. Word不变

  2. WordDao不变

  3. WordDatabase,采用单例模式

    @Database(entities = {Word.class}, version = 1, exportSchema = false)
    public abstract class WordDatabase extends RoomDatabase {
    
        private static WordDatabase INSTANCE;
    
    
        /**
        * @Description 单例获取
        * @date 2020/11/17 8:14
        * @param context
        * @return
        */
        public static synchronized WordDatabase getDatabase(Context context){
            if(INSTANCE == null){
                INSTANCE = Room.databaseBuilder(context, WordDatabase.class, "word_database")
                    .allowMainThreadQueries()
                    .build();
            }
            return INSTANCE;
        }
    
        public abstract  WordDao getWordDao();
    }
    
  4. WordRepository,封装数据库的操作

    public class WordRepository {
    
        private LiveData<List<Word>> allWordsLive;
    
        private WordDao wordDao;
    
        public WordRepository(Context context) {
            WordDatabase wordDatabase = WordDatabase.getDatabase(context);
            wordDao = wordDatabase.getWordDao();
            allWordsLive = wordDao.getAllWords();
        }
    
        public LiveData<List<Word>> getAllWordsLive() {
            return allWordsLive;
        }
    
        public void insertWords(Word... words) {
            new InsertAsyncTask(wordDao).doInBackground(words);
        }
    
        public void updateWords(Word... words) {
            new UpdateAsyncTask(wordDao).doInBackground(words);
        }
    
        public void deleteWords(Word... words) {
            new DeleteAsyncTask(wordDao).doInBackground(words);
        }
    
        public void deleteAllWords() {
            new DeleteAllAsyncTask(wordDao).doInBackground();
        }
    
        static class InsertAsyncTask extends AsyncTask<Word, Void, Void> {
    
            private WordDao wordDao;
    
            public InsertAsyncTask(WordDao wordDao) {
                this.wordDao = wordDao;
            }
    
            @Override
            protected Void doInBackground(Word... words) {
                wordDao.insertWords(words);
                return null;
            }
        }
    
        static class UpdateAsyncTask extends AsyncTask<Word, Void, Void> {
    
            private WordDao wordDao;
    
            public UpdateAsyncTask(WordDao wordDao) {
                this.wordDao = wordDao;
            }
    
            @Override
            protected Void doInBackground(Word... words) {
                wordDao.updateWords(words);
                return null;
            }
        }
    
        static class DeleteAsyncTask extends AsyncTask<Word, Void, Void> {
    
            private WordDao wordDao;
    
            public DeleteAsyncTask(WordDao wordDao) {
                this.wordDao = wordDao;
            }
    
            @Override
            protected Void doInBackground(Word... words) {
                wordDao.deleteWords(words);
                return null;
            }
        }
    
        static class DeleteAllAsyncTask extends AsyncTask<Void, Void, Void> {
    
            private WordDao wordDao;
    
            public DeleteAllAsyncTask(WordDao wordDao) {
                this.wordDao = wordDao;
            }
    
            @Override
            protecteds Void doInBackground(Void... voids) {
                this.wordDao.deleteAllWords();
                return null;
            }
        }
    }
    

    注意一点:AsyncTask已经被弃用了

  5. WordViewModel

    public class WordVIewModel extends AndroidViewModel {
    
        private WordRepository wordRepository;
    
        public WordVIewModel(@NonNull Application application) {
            super(application);
            wordRepository = new WordRepository(application);
    
        }
    
        public LiveData<List<Word>> getAllWordsLive() {
            return wordRepository.getAllWordsLive();
        }
    
    
        public void insertWords(Word... words) {
            wordRepository.insertWords(words);
        }
    
        public void updateWords(Word... words) {
            wordRepository.updateWords(words);
        }
    
        public void deleteWords(Word... words) {
            wordRepository.deleteWords(words);
        }
    
        public void deleteAllWords() {
            wordRepository.deleteAllWords();
        }
    }
    
  6. MainActivity

    public class MainActivity extends AppCompatActivity {
    
    
        TextView textView;
        Button insertButton,updateButton,clearButton,deleteButton;
    
        private WordVIewModel wordVIewModel;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            wordVIewModel = new ViewModelProvider(this).get(WordVIewModel.class);
    
            textView = findViewById(R.id.textView2);
            insertButton = findViewById(R.id.insertButton);
            updateButton = findViewById(R.id.updateButton);
            clearButton = findViewById(R.id.clearButton);
            deleteButton = findViewById(R.id.deleteButton);
    
    
    
            wordVIewModel.getAllWordsLive().observe(this,(words)->{
                StringBuilder text = new StringBuilder();
                for (int i=words.size()-1;i>=0;--i) {
                    text.append(words.get(i).getId()).append(":").append(words.get(i).getWord()).append(" = ").append(words.get(i).getChineseMeaning()).append(";\n");
                }
                textView.setText(text.toString());
            });
    
            //插入操作
            insertButton.setOnClickListener(v->{
                Word word1 = new Word("Hello","你好");
                Word word2 = new Word("World","世界");
                wordVIewModel.insertWords(word1,word2);
            });
    
            //更新操作
            updateButton.setOnClickListener(v->{
                Word word = new Word("spring","春天");
                word.setId(1);
                wordVIewModel.updateWords(word);
            });
    
            //清除操作
            clearButton.setOnClickListener(v->{
                wordVIewModel.deleteAllWords();
            });
    
            //删除操作
            deleteButton.setOnClickListener(v->{
                wordVIewModel.deleteWords(new Word().setIdc(10));
            });
        }
    }
    

RecyclerView

RecyclerView的优点

  1. 进行item回收复用
  2. RecyclerView封装了viewholder的回收复用,也就是说RecyclerView标准化了ViewHolder,编写Adapter面向的是ViewHolder而不再是View了,复用的逻辑被封装了,写起来更加简单。
  3. 提供了一种插拔式的体验,高度的解耦,异常的灵活,针对一个Item的显示RecyclerView专门抽取出了相应的类,来控制Item的显示,使其的扩展性非常强。例如:你想控制横向或者纵向滑动列表效果可以通过LinearLayoutManager这个类来进行控制(与GridView效果对应的是GridLayoutManager,与瀑布流对应的还StaggeredGridLayoutManager等),也就是说RecyclerView不再拘泥于ListView的线性展示方式,它也可以实现GridView的效果等多种效果。你想控制Item的分隔线,可以通过继承RecyclerView的ItemDecoration这个类,然后针对自己的业务需求去抒写代码。
  4. 可以控制Item增删的动画,可以通过ItemAnimator这个类进行控制,当然针对增删的动画,RecyclerView有其自己默认的实现。

首先修改一下插入操作代码

insertButton.setOnClickListener(v->{
    String[] english = {
        "Hello",
        "World",
        "Android",
        "Google",
        "Studio",
        "Project",
        "Database",
        "Recycler",
        "View",
        "String",
        "Value",
        "Integer"
    };
    String[] chinese = {
        "你好",
        "世界",
        "安卓系统",
        "谷歌公司",
        "工作室",
        "项目",
        "数据库",
        "回收站",
        "视图",
        "字符串",
        "价值",
        "整数类型"
    };
    for(int i=0;i<chinese.length;i++){
        wordVIewModel.insertWords(new Word(english[i], chinese[i]));
    }
});

使用RecyclerView

创建layout

创建元素的layout

创建一个Adapter

package com.example.roombasic;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.List;

/**
 * @author codekiller
 * @date 2020/11/17 9:20
 * @Description TODO
 */
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {

    List<Word> allWords = new ArrayList<>();

    public void setAllWords(List<Word> allWords){
        this.allWords = allWords;
    }


    @NonNull
    @Override
    public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
        View itemView = layoutInflater.inflate(R.layout.cell_normal,parent,false);
        return new MyViewHolder(itemView);
    }

    @Override
    public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
        Word word = allWords.get(position);
        holder.textViewNumber.setText(String.valueOf(position+1));
        holder.textViewEnglish.setText(word.getWord());
        holder.textViewChinese.setText(word.getChineseMeaning());
    }

    @Override
    public int getItemCount() {
        return allWords.size();
    }

    static class MyViewHolder extends RecyclerView.ViewHolder{

        TextView textViewNumber, textViewEnglish, textViewChinese;

        public MyViewHolder(@NonNull View itemView) {
            super(itemView);
            textViewNumber = itemView.findViewById(R.id.textViewNumber);
            textViewChinese = itemView.findViewById(R.id.textViewChinese);
            textViewEnglish = itemView.findViewById(R.id.textViewEnglish);

        }
    }

}

修改MainAdapter代码

package com.example.roombasic;

import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.room.Room;
import androidx.room.util.StringUtil;
import android.os.AsyncTask;
import android.os.Bundle;
import android.widget.Button;
import android.widget.TextView;

import java.util.List;

public class MainActivity extends AppCompatActivity {


    RecyclerView recyclerView;
    Button insertButton,updateButton,clearButton,deleteButton;
    MyAdapter myAdapter;

    private WordVIewModel wordVIewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        wordVIewModel = new ViewModelProvider(this).get(WordVIewModel.class);


        insertButton = findViewById(R.id.insertButton);
        updateButton = findViewById(R.id.updateButton);
        clearButton = findViewById(R.id.clearButton);
        deleteButton = findViewById(R.id.deleteButton);

        recyclerView = findViewById(R.id.recyclerView);
        myAdapter = new MyAdapter();

        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        recyclerView.setAdapter(myAdapter);

        wordVIewModel.getAllWordsLive().observe(this,(words)->{
            myAdapter.setAllWords(words);
            myAdapter.notifyDataSetChanged();
        });

        //插入操作
        insertButton.setOnClickListener(v->{
            String[] english = {
                "Hello",
                "World",
                "Android",
                "Google",
                "Studio",
                "Project",
                "Database",
                "Recycler",
                "View",
                "String",
                "Value",
                "Integer"
            };
            String[] chinese = {
                "你好",
                "世界",
                "安卓系统",
                "谷歌公司",
                "工作室",
                "项目",
                "数据库",
                "回收站",
                "视图",
                "字符串",
                "价值",
                "整数类型"
            };
            for(int i=0;i<chinese.length;i++){
                wordVIewModel.insertWords(new Word(english[i], chinese[i]));
            }
        });

        //更新操作
        updateButton.setOnClickListener(v->{
            Word word = new Word("spring","春天");
            word.setId(1);
            wordVIewModel.updateWords(word);
        });

        //清除操作
        clearButton.setOnClickListener(v->{
            wordVIewModel.deleteAllWords();
        });

        //删除操作
        deleteButton.setOnClickListener(v->{
            wordVIewModel.deleteWords(new Word().setIdc(10));
        });
    }
}

CardView,波纹效果和网页跳转

创建一个新的layout,记得三个textView的id,和cell_normal中相同的就行

设置一下margin,CardView下修改

修改Adapter

@NonNull
@Override
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
    View itemView = layoutInflater.inflate(R.layout.cell_card,parent,false);
    return new MyViewHolder(itemView);
}

波纹效果

相当于

再修改背景样式

运行之后发现,可点击,但是波纹效果是在后面的,此时要修改一下前景


效果图演示

这里有一个小功能,就是跳转到有道词典了。修改adapter

@Override
public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
    Word word = allWords.get(position);
    holder.textViewNumber.setText(String.valueOf(position+1));
    holder.textViewEnglish.setText(word.getWord());
    holder.textViewChinese.setText(word.getChineseMeaning());

    holder.itemView.setOnClickListener(v->{
        Uri uri = Uri.parse("https://m.youdao.com/dict?le=eng&q=" + holder.textViewEnglish.getText());
        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.setData(uri);
        holder.itemView.getContext().startActivity(intent);
    });
}

增加隐藏

  1. 增加一个switch

  2. 增加一个字段

    @ColumnInfo(name = "chinese_invisible")
    private boolean chineseInvisible;
    
  3. 修改adapter

    public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
    
        private List<Word> allWords = new ArrayList<>();
        private WordVIewModel wordVIewModel;
    
        public MyAdapter(WordVIewModel wordVIewModel) {
            this.wordVIewModel = wordVIewModel;
        }
    
        public void setAllWords(List<Word> allWords){
            this.allWords = allWords;
        }
    
    
        @NonNull
        @Override
        public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
            LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
            View itemView = layoutInflater.inflate(R.layout.cell_card2,parent,false);
            return new MyViewHolder(itemView);
        }
    
        @Override
        public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
            Word word = allWords.get(position);
            holder.textViewNumber.setText(String.valueOf(position+1));
            holder.textViewEnglish.setText(word.getWord());
            holder.textViewChinese.setText(word.getChineseMeaning());
    
            holder.aSwitchChineseInvisible.setOnCheckedChangeListener(null);
    
            //控制显示和隐藏中文
            if(word.isChineseInvisible()){
                //GONE 会隐藏,而且会释放空间(改变位置),而invisible仅仅是不显示。
                holder.textViewChinese.setVisibility(View.GONE);
                holder.aSwitchChineseInvisible.setChecked(true);
            } else {
                holder.textViewChinese.setVisibility(View.VISIBLE);
                holder.aSwitchChineseInvisible.setChecked(false);
            }
    
            //进行有道词典搜索
            holder.itemView.setOnClickListener(v->{
                Uri uri = Uri.parse("https://m.youdao.com/dict?le=eng&q=" + holder.textViewEnglish.getText());
                Intent intent = new Intent(Intent.ACTION_VIEW);
                intent.setData(uri);
                holder.itemView.getContext().startActivity(intent);
            });
    
            //switch监听
            holder.aSwitchChineseInvisible.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
                @Override
                public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                    if(isChecked){
                        holder.textViewChinese.setVisibility(View.GONE);
                        word.setChineseInvisible(true);
                        wordVIewModel.updateWords(word);
                    }else{
                        holder.textViewChinese.setVisibility(View.VISIBLE);
                        word.setChineseInvisible(false);
                        wordVIewModel.updateWords(word);
                    }
    
                }
            });
        }
    
        @Override
        public int getItemCount() {
            return allWords.size();
        }
    
        static class MyViewHolder extends RecyclerView.ViewHolder{
    
            TextView textViewNumber, textViewEnglish, textViewChinese;
    
            Switch aSwitchChineseInvisible;
    
            public MyViewHolder(@NonNull View itemView) {
                super(itemView);
                textViewNumber = itemView.findViewById(R.id.textViewNumber);
                textViewChinese = itemView.findViewById(R.id.textViewChinese);
                textViewEnglish = itemView.findViewById(R.id.textViewEnglish);
                aSwitchChineseInvisible = itemView.findViewById(R.id.chineseInvisible);
            }
        }
    
    }
    

这一点尤为重要

holder.aSwitchChineseInvisible.setOnCheckedChangeListener(null);

  1. 修改MainActvity

    一定要避免两次刷新,不然后卡顿

    wordVIewModel.getAllWordsLive().observe(this,(words)->{
        int temp = myAdapter.getItemCount();
        myAdapter.setAllWords(words);
    
        //避免两种刷新冲突
        if(temp != words.size()) {
            myAdapter.notifyDataSetChanged();
        }
    });
    

数据迁移

我们此时有一个需求,在原有的word表中增加一个字段,应该怎么做?

@PrimaryKey(autoGenerate = true)
private int id;

@ColumnInfo(name = "english_word")
private String word;

@ColumnInfo(name = "chinese_meaning")
private String chineseMeaning;

@ColumnInfo(name = "bar_data")
private boolean bar;
破坏式迁移

修改数据库的version和增加.fallbackToDestructiveMigration(),这种方式就是将现有结构清空,然后创建新的数据库,是一种破坏式的创建。不推荐

@Database(entities = {Word.class}, version = 2, exportSchema = false)
public abstract class WordDatabase extends RoomDatabase {

    private static WordDatabase INSTANCE;


    /**
    * @Description 单例获取
    * @date 2020/11/17 8:14
    * @param context
    * @return
    */
    public static synchronized WordDatabase getDatabase(Context context){
        if(INSTANCE == null){
            INSTANCE = Room.databaseBuilder(context, WordDatabase.class, "word_database")
                            //.allowMainThreadQueries()
                            .fallbackToDestructiveMigration()
                            .build();
        }
        return INSTANCE;
    }

    public abstract  WordDao getWordDao();
}

SQL语句方式(※)

其实就是运行一个sql语句,对表结构进行修改

@Database(entities = {Word.class}, version = 2, exportSchema = false)
public abstract class WordDatabase extends RoomDatabase {

    private static WordDatabase INSTANCE;


    /**
    * @Description 单例获取
    * @date 2020/11/17 8:14
    * @param context
    * @return
    */
    public static synchronized WordDatabase getDatabase(Context context){
        if(INSTANCE == null){
            INSTANCE = Room.databaseBuilder(context, WordDatabase.class, "word_database")
                            //.allowMainThreadQueries()
                            .addMigrations(MIGRATION_1_2)
                            .build();
        }
        return INSTANCE;
    }

    public abstract  WordDao getWordDao();

    static final Migration MIGRATION_1_2 = new Migration(1, 2) {
        @Override
        public void migrate(@NonNull SupportSQLiteDatabase database) {
            database.execSQL("alter table word add  column bar_data INTEGER not null default 1");
        }
    };
}

查看结果,将我们的数据库表信息存储到本地,存储前记得刷新!!!然后用DB Brower for SQLite打开word_database文件。

通过这种方式,我们也能执行任何sql语句


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值