控件拖拽置换位置

本文介绍了一个在Android中实现控件拖拽并置换位置的方法。关键逻辑包括使用getRawX()和getRawY()获取手指屏幕坐标,以及理解控件相对于父控件的坐标。注意布局文件中的属性值需在界面加载后获取。虽然实现较为简单,没有处理不同大小的控件和动画效果,但为开发者提供了一个基本的思路。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

简单的一个控件拖拽交换位置Demo,有些场景下会用到,关于此类功能网上有很多例子,而且Google官方API中也有相应的接口,对这种业务需求进行了一定逻辑封装。这里没有采用官方接口,单纯的从触摸事件入手来简单的实现控件位置交换。
写代码之前先理清楚实现的思路,这里从控件的触摸事件入手,触摸事件有ACTION_DOWN、ACTION_MOVE、ACTION_UP这几个状态,下面先把实现逻辑的思路写出来:
1.界面加载完成后用一个List记录各个子控件相对于父控件的坐标,同时将这些子控件放入一个List中备用。
2.分别给每个子控件添加触摸事件,拖拽交换的主要逻辑在触摸事件中完成。
3.触摸事件:
    当手指落下时:记录当前手指坐标startX、startY,记录当前触摸的控件a的初始位置;
    当手指开始移动:移动过程中onTouch方法会不断执行,这里要做的就是不断的用View的layout(l,t,r,b)方法不断的将被触摸的控件定位到手指移动过的地方,产生的效果就是被触摸的控件a跟随手指进行滑动。获取手指的实时位置与ACTION_DOWN时的位置相减得出手指移动的差值,获取控件a四条边相对于父控件的坐标位置,将这四个坐标分别加上这个差值得出新的四边坐标,调用View的layout(l,t,r,b)方法重新定位控件a,然后重新给startX、startY赋值。
    当手指抬起:判断当前手指位置是否在某一个子控件b的四条边坐标内部,如果不在则让控件a定位回初始位置,如果在则让控件a和控件b交换位置。

有了以上的逻辑分析是不是思路清晰了许多呢,下面就一步步来实现上面的逻辑
首先定义一个布局文件,其中包含几个Button:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:id="@+id/ll_main"
    android:orientation="vertical"
    android:paddingTop="@dimen/DIMEN_80PX"
    >

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="button1"
        android:paddingTop="@dimen/DIMEN_40PX"
        android:paddingBottom="@dimen/DIMEN_40PX"
        />
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingTop="@dimen/DIMEN_40PX"
        android:paddingBottom="@dimen/DIMEN_40PX"
        android:text="button2"
        />
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingTop="@dimen/DIMEN_40PX"
        android:paddingBottom="@dimen/DIMEN_40PX"
        android:text="button3"
        />
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingTop="@dimen/DIMEN_40PX"
        android:paddingBottom="@dimen/DIMEN_40PX"
        android:text="button4"
        />
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingTop="@dimen/DIMEN_40PX"
        android:paddingBottom="@dimen/DIMEN_40PX"
        android:text="button5"
        />
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingTop="@dimen/DIMEN_40PX"
        android:paddingBottom="@dimen/DIMEN_40PX"
        android:text="button6"
        />
</LinearLayout>

下面是MainActivity类

package com.donz.exchangebutton;

import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.view.MotionEvent;
import android.view.View;
import android.widget.LinearLayout;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class MainActivity extends ActionBarActivity {
    public static final String TAG = "MainActivity";
    private LinearLayout ll_main;
    private List<Map<String,Integer>> buttons_locations;

    private int parentt;
    private int parentl;
    private View[] views;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        buttons_locations = new ArrayList<>();
        initViews();
        initTouchEvent();
    }

    private void initTouchEvent() {
        for(int j = 0;j<ll_main.getChildCount();j++){
//            给每个按钮绑定点击事件
            ll_main.getChildAt(j).setOnTouchListener(new View.OnTouchListener() {

                int startX;
                int startY;
                int x;
                int y;

                @Override
                public boolean onTouch(View v, MotionEvent event) {
                    v.bringToFront();
                    switch (event.getAction()) {
                        case MotionEvent.ACTION_DOWN:
//                      手指落下的起始位置
                            startX = (int) event.getRawX();
                            startY = (int) event.getRawY();
                            int[] location = new int[2];
                            v.getLocationOnScreen(location);
                            x = location[0];
                            y = location[1];
                            break;
                        case MotionEvent.ACTION_MOVE:
                            int newX = (int) event.getRawX();
                            int newY = (int) event.getRawY();
//                      计算出手指在x和y的偏移量
                            int dex = newX - startX;
                            int dey = newY - startY;
//                      实时计算出button的上下左右位置,相对于父元素
                            int l = v.getLeft();
                            int t = v.getTop();
                            int r = v.getRight();
                            int b = v.getBottom();
//                      button在手指移动后应该在的新位置
                            int newl = l + dex;
                            int newr = r + dex;
                            int newt = t + dey;
                            int newb = b + dey;
//                      将button布局到新位置
                            v.layout(newl, newt, newr, newb);

//                      重置手指的起始位置
                            startX = (int) event.getRawX();
                            startY = (int) event.getRawY();
                            break;
                        case MotionEvent.ACTION_UP:
                            int newfingerX = (int) event.getRawX();
                            int newfingerY = (int) event.getRawY();

//                      遍历整个位置列表比较是否当前手指位置和别的button位置有重合,如果有就互换位置否则将该button放回原位置
                            for (int i = 0; i < buttons_locations.size(); i++) {
                                if(i==v.getTag()){
                                    continue;
                                }
                                Map<String, Integer> m = buttons_locations.get(i);
                                int btnl = m.get("l");
                                int btnt = m.get("t");
                                int btnr = m.get("r");
                                int btnb = m.get("b");
                                if (newfingerX > btnl && newfingerY > btnt && newfingerX < btnr && newfingerY < btnb) {
//                                  在某个button范围内
                                    v.layout(btnl - parentl, btnt - parentt, btnr - parentl, btnb - parentt);
                                    views[i].layout(x - parentl, y - parentt, x + v.getWidth() - parentl, y + v.getHeight() - parentt);
//                                  替换view的值
                                    views[(int)(v.getTag())] = views[i];
                                    views[(int)(v.getTag())].setTag(v.getTag());
                                    views[i] = v;
                                    views[i].setTag(i);
//                                  替换map的值
                                    buttons_locations.set(i,buttons_locations.get((int)(v.getTag())));
                                    buttons_locations.set((int)(v.getTag()),m);
                                    return true;
                                }
                            }

//                      返回原位置
                            v.layout(x - parentl, y - parentt, x +v.getWidth() - parentl, y+v.getHeight() - parentt);

                            break;
                    }
                    return true;
                }
            });
        }

    }

    @Override
    public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);
        if(hasFocus){
            int[] parentLocation = new int[2];
            ll_main.getLocationOnScreen(parentLocation);
            parentl = parentLocation[0];
            parentt = parentLocation[1];
            int count = ll_main.getChildCount();
            views = new View[count];
            for (int i = 0;i<count;i++){
                Map<String,Integer> map = new HashMap<>();
                int[] location = new int[2];
                ll_main.getChildAt(i).getLocationOnScreen(location);
                ll_main.getChildAt(i).setTag(i);
                map.put("l", location[0]);
                map.put("t",location[1]);
                map.put("r",ll_main.getChildAt(i).getWidth()+location[0]);
                map.put("b",ll_main.getChildAt(i).getHeight()+location[1]);
                buttons_locations.add(map);
                views[i] = ll_main.getChildAt(i);
            }

        }
    }

    private void initViews() {
        ll_main = (LinearLayout) findViewById(R.id.ll_main);
    }
}

关键逻辑在代码中都给了详细的注释,相信不难看懂,需要注意的几点:

1.getRawX()、getRawY()方法是获取手指在手机屏幕中的坐标,而getX()、getY()则是获取点相对于父控件的坐标。getLeft()、getRight()、getTop()、getBottom()分别是获取控件四条边相对于控件父控件的坐标位置,他们之间和控件的宽度高度使可以换算的。
2.布局文件中控件的一些属性值必须在界面加载完成后才能通过方法获取到,如果直接在Activity的onCreate()方法中获取是获取不到的。这是因为布局渲染需要时间,详情可以去看View的onMeasure()和onLayout()方法。

这里只是提供一个思路和大概的实现,各方面都比较粗糙,因为置换的是空间本身而不是文本内容,各个控件的大小也没处理,如果大小不一的话会出问题。体验方面可以添加一些动画效果。

如果这篇文章内容对您有帮助,那就请动一下手在下方文章结尾后的按钮处给顶一下吧,不胜感激!

源码链接(Android Studio可以直接导入,Eclipse嫌麻烦的话直接建个新项目代码粘过去即可):

源码下载地址点我,我

代码使用 PyQt5 创建统一界面,通过选项卡切换不同实验;一定要严格遵守要求,把所有要求都实现,不要遗漏,一定要按要求,页面的布局要简约大方【要求设计一个整合系统 使得每个项目实验可以在一个整体系统中进行演示。 (可视化界面内容格式,不做硬性要求) 实验项目一:动态资源分配算法模拟--银行家算法 实验内容: 主要用于解决多种资源被多个独立执行的进程共享的安全算法。采用矩阵存储资源的数据,通过对系统资源预分配后检查系统状态,以避免死锁的产生。 学习要求: 1.资源种类与数目可在界面进行设置,在资源分配过程中可以随时增加进程及其对资源的需求。 2.可读取样例数据(要求存放在外部文件中)进行资源种类、数目与进程数的初始化。 3.在资源分配过程中可以随时进行系统安全状态检测。 4.如果能够通过系统安全状态检测,则系统对该进程进行资源分配;当进程满足所有资源分配后能够自行释放所有资源,退出资源竞争。 5.要求进行安全性检查时按指定策略顺序进行,即按每个进程当前Need数由小至大进行排序,如果Need数相同,则按序号由小至大进行排序; 6.具有一定的数据容错性。 实验检查要求: 假设有 5 个进程(P0, P1, P2, P3, P4)。 系统提供 3 种资源(A, B, C),资源总量分别为 A=10, B=5, C=7。 输入:一组代表性的数据(下图展示) Max 最大需求矩阵: P0: [7, 5, 3] P1: [3, 2, 2] P2: [9, 0, 2] P3: [2, 2, 2] P4: [4, 3, 3] Allocation 当前分配矩阵: P0: [0, 1, 0] P1: [2, 0, 0] P2: [3, 0, 2] P3: [2, 1, 1] P4: [0, 0, 2] Available: [3, 3, 2] (表示系统中可用的 3 个 A 资源,3 个 B 资源,2 个 C 资源) 输出:安全序列 安全序列会有五种情况,均正确: 1、P3 -> P1 -> P4 -> P2 -> P0 2、P3 -> P1 -> P4 -> P0 -> P2 3、P3 -> P1 -> P0 -> P2 -> P4 4、P1 -> P3 -> P4 -> P2 -> P0 5、P1 -> P3 -> P0 -> P2 -> P4 下面请同学们尝试将可用资源能否有安全序列 比如:Available: [3, 1, 2] ->没有安全序列,发生死锁 实验项目二:通用处理机调度演示程序 实验内容:设计一个模拟处理机调度算法,以巩固和加深处理机调度的概念。 实验要求: 1. 进程调度算法包括:时间片轮转算法、先来先服务算法、短作业优先算法、静态优先权优先调度算法、高响应比调度算法 。 2. 每一个进程有一个PCB,其内容可以根据具体情况设定。 3. 进程数、进入内存时间、要求服务时间、作业大小、优先级等均可以在界面上设定。 4. 可读取样例数据(要求存放在外部文件中)进行进程数、进入内存时间、时间片长度、作业大小、进程优先级的初始化 5. 可以在运行中显示各进程的状态:就绪、执行(由于不要求设置互斥资源与进程间的同步关系,故只有两种状态) 6. 采用可视化界面,可在进程调度过程中随时暂停调度,查看当前进程的状态及相应的阻塞队列。 7. 有性能比较功能,可比较同一组数据在不同调度算法下的平均周转时间。 实验检查要求: 输入:n个进程的基本信息:进程号、到达时间、服务时长(作业大小)、优先级别;(建议进程信息从文件读取) 输出:显示调度过程, 进程数:4 时间片长度:2(仅适用于时间片轮转算法) 初始的进程列表及其属性: (从本文中读入,效果如下) 示范样例: P1:到达时间 0,运行时间 5,优先级 3 P2:到达时间 1,运行时间 3,优先级 2 P3:到达时间 2,运行时间 8,优先级 1 P4:到达时间 3,运行时间 6,优先级 4 操作:选择任意算法(时间片轮转算法、先来先服务算法、短作业优先算法、静态优先权优先调度算法、高响应比调度算法) 部分算法的结果输出: 1. 先来先服务(FCFS): 执行顺序:P1 -> P2 -> P3 -> P4 完成时间:P1 = 5, P2 = 8, P3 = 16, P4 = 22 周转时间:P1 = 5, P2 = 7, P3 = 14, P4 = 19 等待时间:P1 = 0, P2 = 4, P3 = 6, P4 = 13 平均周转时间:11.25 平均等待时间:5.75 2. 最短作业优先(SJF): 执行顺序:P1 -> P2 -> P4 -> P3 完成时间:P1 = 5, P2 = 8, P4 = 14, P3 = 22 周转时间:P1 = 5, P2 = 7, P4 = 11, P3 = 20 等待时间:P1 = 0, P2 = 4, P4 = 5, P3 = 12 平均周转时间:10.75 平均等待时间:5.25 3. 静态优先权优先调度: 执行顺序:P1 → P3 → P2 → P4 完成时间:P1 = 5, P3 = 13, P2 = 16, P4 = 22 周转时间:P1 = 5, P3 = 11, P2 = 15, P4 = 17 等待时间:P1 = 0, P3 = 3, P2 = 12, P4 = 13 平均周转时间:12.5 平均等待时间:7 4. 时间片轮转(RR, 时间片 = 2): 执行顺序: 时间段 进程 0 - 2 P1 2 - 4 P2 4 - 6 P3 6 - 8 P1 8 -10 P4 10-11 P2 11-13 P3 13-14 P1 14-16 P4 16-18 P3 18-20 P4 20-22 P3 完成时间:P1 =14, P2 = 11, P3 = 22, P4 = 20 周转时间:P1 = 14, P2 = 0, P3 = 22, P4 = 17 等待时间:P1 = 9, P2 = 7, P3 = 12, P4 = 11 平均周转时间:15.25 平均等待时间:9.75 5.高响应比调度算法(HRRN) 执行顺序:P1 → P2 → P4 → P3 完成时间:P1 = 5, P3 =8, P2 = 14, P4 = 22 周转时间:P1 = 5, P3 = 7, P2 = 11, P4 =20 等待时间:P1 = 0, P3 = 4, P2 = 5, P4 = 12 平均周转时间 = (5 + 7 + 11 + 20) / 4 = 10.75 平均等待时间 = (0 + 4 + 5 + 12) / 4 = 5.25 实验项目三:进程间通信 实验内容:在Windows环境下,1个读者和N个写者随机读写一个共享缓冲区的问题。缓冲区非满可以连续写(满时等待),缓冲区非空可以连续读(空时等待);读和写的顺利随机交替,按照写入到缓冲区的先后顺序读出的缓冲区的每一个数据,数据读出后该缓冲区即为空闲等待新数据写入;显示读者和写者的数据写入和读取的过程。编程实现基于共享内存的进程间通信问题。 实验要求: (1)当两个进程通信时需满足进程同步机制。 (2)当多个进程(大于2,约定只有一个读者和多个写者)通信时需使用信号量机制实现互斥操作。 (3)编程语言不限。 实验检查要求: 输入:参数设置1个读者,n个写者,缓冲区大小可以设置。 操作:执行读/写过程 输出: 显示读者和写者交互过程,由读者按照写入到缓冲区先后顺序读出每一个数据。 示范样例(尝试实现可视化): 输入: 读者数:1 写者数:3 缓冲区大小:3 (一般设为2的倍数,16) 输出: 时间 0.0s: 缓冲区状态: [ ][ ][ ] 写者1 写入了数据 'A'。 缓冲区状态: [A][ ][ ] 时间 0.1s: 写者2 写入了数据 'B'。 缓冲区状态: [A][B][ ] 时间 0.2s: 写者3 写入了数据 'C'。 缓冲区状态: [A][B][C] 时间 0.3s: 缓冲区已满,写者1、写者2、写者3 正在等待。 时间 0.4s: 读者读取了数据 'A'。 缓冲区状态: [ ][B][C]   实验项目四:存储管理动态分配算法模拟 实验内容:设计主界面以灵活选择某算法,且以下算法都要实现:首次适应算法、循环首次适应算法、最佳适应算法; 实验要求: 用一种结构化高级语言构造分区描述器,编制动态分区分配算法和回收算法模拟程序,并掌握分配算法的特点,提高编程技巧和对算法的理解和掌握。 实验检查要求: 输入:通过参数初始存储区域的大小(10000KB)。 操作:(1)可以通过界面或者命令行对整个存储空间进行分区,模拟硬盘分析A、B、C,也可以删除和合并分区; (2)根据“首次适应算法、循环首次适应算法、最佳适应算法”,选择“A、B、C”某一个分区,进行存储空间的分配。实例:分配4个作业,大小分别是50KB,100KB,150KB,200KB;然后释放作业4(200KB),释放作业1(50KB)和作业2(100KB),同一分区的连续空闲空间要自动合并。 输出: 显示读者和写者交互过程,由读者按照写入到缓冲区先后顺序读出每一个数据。 初始存储空间大小:10000KB +-----------+-----------+-----------+ | 分区 A | 分区 B | 分区 C | | 4000 KB | 3000 KB | 3000 KB | +-----------+-----------+-----------+ 1. 第一次分配:分配4个作业,大小分别为 50、100、150、200。 内存状态: [作业1 50KB][作业2 100KB][作业3 150KB][作业4 200KB][空闲3500KB] 空闲块表示意图: +------------+--------------+-----------+ | 空闲块编号 | 起始地址 (KB)| 大小 (KB) | +------------+--------------+-----------+ | 1 | 500 | 3500 | +------------+--------------+-----------+ 2. 操作:释放作业4 内存状态: [作业1 50KB][作业2 100KB][作业3 150KB][空闲3700KB] 空闲块表示意图: +------------+--------------+-----------+ | 空闲块编号 | 起始地址 (KB) | 大小 (KB) | +------------+--------------+-----------+ | 1 | 300 | 3700 | +------------+--------------+-----------+ 3. 操作:释放作业1和作业2 内存状态: [作业1 50KB][作业2 100KB][作业3 150KB][空闲200KB][空闲3500KB] 空闲块表示意图: +------------+--------------+-----------+ | 空闲块编号 | 起始地址 (KB) | 大小 (KB)| +------------+--------------+-----------+ | 1 | 300 | 3700 | | 2 | 0 | 150 | +------------+--------------+-----------+ 最终内存状态(A区): +------------+-------------+---------------+ | 空闲150KB | 作业3 150KB | 空闲3700KB | +------------+-------------+---------------+ | 地址 0-149 | 地址150-299 | 地址300-3999 | +------------+-------------+---------------+   实验项目五:存储管理之虚拟存储器模拟--页面置换算法 实验内容:编程序实现先进先出算法(FIFO)、最近最久未使用算法(LRU)算法、最佳置换算法(OPT)的具体实现过程,并计算访问命中率。 实验要求: 1.设计主界面以灵活选择某算法,且以上算法都要实现。 2. 用随机数方法产生页面走向。 3. 假定初始时页面都不在内存。 实验检查要求: 页面引用序列: 7, 0, 1, 2, 0, 3, 0, 4, 2, 3, 0, 3, 2, 1, 2, 0, 1, 7, 0, 1 物理内存框数为 3 结果对比: 算法 缺页次数 总请求次数 缺页率 命中率 FIFO 15 20 75 25 LRU 12 20 60 40 OPT 9 20 45 55   实验项目六:文件系统设计 实验内容:以内存作为存储区,模拟UNIX的文件系统结构,设计一个简单的文件系统,并提供以下的文件和目录操作接口:创建文件、删除文件、打开文件、关闭文件、读文件、写文件。 实验要求: 设计文件系统的数据结构和数据结构间的关系,设计文件的基本操作算法,完成系统的总控模块编程和各个操作模块编程。上机测试各个模块,没执行一个操作,打印有关数据结构的内容,判断操作的正确性。 实验检查要求: 假设我们需要在 D1 目录下创建一个子目录 D2,然后在 D2 目录下创建一个文件 D1,并防止同名文件或目录的创建。 1. 创建目录 D1: 当前结构: / ├── D1 2. 进入 D1,创建目录 D2: 当前结构: / ├── D1 └── D2 3. 进入 D2,创建文件 D1: 当前结构: / ├── D1 └── D2 └── D1 (文件) 4. 尝试在 D2 下再次创建 D1 文件(同名文件): 错误:在目录 D2 下已存在名为 D1 的文件或目录 5. 尝试在D1 文件内写入内容:“ABCD” 6. 读取D1 文件内容   实验项目七:磁盘调度管理 实验内容:设计一个磁盘调度系统,针对磁盘访问序列,可以选择先来先服务算法(FCFS)、最短寻道时间优先算法(SSTF)、扫描算法(SCAN)、循环扫描算法(CSCAN)来实现。 实验要求: 1.系统主界面可以灵活选择某种算法。 2.每种调度算法实现之后,要计算出每种算法的平均寻道长度,并比较结果。 3.采用改进算法之后是否会使性能提高? 实验检查要求: 输入: 磁盘访问序列: 例如,给定磁盘请求序列 [98, 183, 37, 122, 14, 124, 65, 67]。 初始磁头位置: 例如,初始磁头位置为 53。 磁盘范围: 例如,磁盘磁道的范围为 0-199。 输出结果对比: 最短寻道时间优先算法(SSTF)最优   实验项目八:多进程同步模拟--桔子苹果问题 实验内容:有两类生产者,一类负责生产桔子,一类负责生产苹果;有两类消费者,一类负责消费桔子,一类负责消费苹果;他们共享一个有20个存储单元的有界缓冲区,每个存储单元只能放入一种产品(桔子/苹果)。 实验要求: 1.二类生产者与二类消费者数目均为20,即20个生产者负责生产桔子,20个生产者负责生产苹果;20个消费者负责消费桔子,20个消费者负责消费苹果 。 2.二类生产者的生产速度与二类消费者的消费速度均可独立在程序界面调节,在运行中,该值调整后立即生效。 3.多个生产者或多个消费者之间必须有共享对缓冲区进行操作的函数代码,同时需要考虑算法的效率性。 4.每个生产者和消费者对有界缓冲区进行操作后,即时显示有界缓冲区的全部内容、当前生产者与消费者的指针位置,以及生产者和消费者线程标识符。 5.采用可视化界面,可在运行过程中随时暂停,查看当前生产者、消费者以及有界缓冲区的状态。 输入: 生产者数:20 消费者数:20 缓冲区大小:20 初始缓冲区状态:空 示范样例 生产者生产桔子和苹果的速率(同学们可以自行调节): 桔子生产者:生产桔子,每秒 2 个。 苹果生产者:生产苹果,每秒 1 个。 样例消费者消费的速率: 桔子消费者:消费桔子,每 1 秒消费 1 个。 苹果消费者:消费苹果,每 1 秒消费 1 个。 输出: 动态展示每个生产者和消费者的操作,缓冲区的实时状态,并输出程序结束时的缓冲区内容和操作日志。(如下图,此时缓冲区中桔子比苹果要多,类似这种效果即可) 缓冲区状态 桔子 桔子 桔子 空 空 空 空 空 空 空 苹果 苹果 空 空 空 空 空 空 空 空 】
最新发布
05-27
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值