17、ListView & GridView

一、ListView

listView是用来解决大量的相似数据显示问题,同时大量的数据会导致不断在内存中创建对象,可能导致OOM。

1.1、listView的属性

image

该控件采用MVC的设计模式,而且还有大量适配器,一般情况是:BaseXXX、BasicXXX、SimpleXXX、DefaultXXX。

除此之外,来看看ListView的常用事件:

(1)setOnclickListener()

(2)setOnItemLongClickListener()

(3)setOnItemClickListener()

(4)setOnScrollListener()

(5)setOnItemSelectedListener()

(6)setOnTouchListener()

1.2、MVC模式的简单理解

7cc5a75e-3cdf-4f97-93fd-11ffdd32891a

  • Model:通常可以理解为数据
  • View:用户的操作接口,说白了就是GUI,应该使用哪种接口组件,组件间的排列位置与顺序都需要设计
  • Controller:控制器,作为model与view之间的枢纽,负责控制程序的执行流程以及对象之间的一个互动

而这个Adapter则是中间的这个Controller的部分: Model(数据) ---> Controller(以什么方式显示到)---> View(用户界面)

1.3、Adapter概念解析

246b7a75-d732-466a-99fd-69d88f6138bb

让我们来看看常用的Adapter:

  • ArrayAdapter:支持泛型操作,最简单的一个Adapter,只能展现一行文字。
  • SimpleAdapter:同样具有良好扩展性的一个Adapter,可以自定义多种效果。
  • SimpleCursorAdapter:用于显示简单文本类型的listView,一般在数据库那里会用到。
  • BaseAdapter:抽象类,实际开发中我们会继承这个类并且重写相关方法,用得最多的一个Adapter。


1、ArrayAdapter:它是BaseAdapter的子类,主要用于存放字符串。
  ArrayAdapter<T>(context:这个参数表示上下文,一般都是this, 
                  textViewResourceId:这个参数是指定自定义的布局文件, 
                  objects):接收的参数,添加到ListView视图中的,类型根据泛型而变化。

public class MainActivity extends Activity {
    private ListView mListView;
    private static final String[] mDatas = {"功能1","功能2","功能3"};
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mListView = (ListView) findViewById(R.id.listview);
        // 设置适配器,第三个数组是根据泛型而变化的
        mListView.setAdapter(new ArrayAdapter<String>(this, R.layout.list_item, mDatas));
    }
}

需要注意的是,上面的list_item布局中,TextView必须作为根节点,否则报错:

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/tv_content"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />

如果需要使用系统自定义的样式,由于ArrayAdapter是单行显示,所以只能用simple_list_item_1

mListView.setAdapter(new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1,
        new String[]{"功能1","功能2","功能3"}));

如果ArrayAdapter想要实现更多的效果则需要自定义ArrayAdapter,不过目前这种方式已经过时。

2、SimpleAdapter:它也是BaseAdapter的子类,用来实现一些图片、文字并排的效果。
  SimpleAdapter(context:上下文
                data:代表整个ListView的List集合,
                resource:自定义的布局文件或系统布局文件, 
                from:对应的key的数组。
                to):对应的value的数组。

public class MainActivity extends Activity {
    private ListView mListView;
    private String[] mDatas = {"声音","显示","存储","电池","应用"};
    private int[] resources = {R.drawable.akb,R.drawable.akc,R.drawable.akd,R.drawable.ake,R.drawable.akf};
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mListView = (ListView) findViewById(R.id.listview);
        // 构建数据模型
        List<Map<String, Object>> lists = new ArrayList<Map<String,Object>>();
        for (int i = 0; i < resources.length; i++) {
            Map<String, Object> maps = new HashMap<String, Object>();
            maps.put("name", mDatas[i]);
            maps.put("drawable", resources[i]);
            lists.add(maps);
        }
        mListView.setAdapter(new SimpleAdapter(this, lists, R.layout.item, 
                new String[]{"name","drawable"}, new int[]{R.id.tv_content,R.id.iv_img}));
        
    }
}

如果需要用到系统的样式,有如下系统样式:

  • simple_list_item_1:单行文本组成
  • simple_list_item_2:两行文本组成
  • simple_list_item_checked:每项都是由一个已选中的列表框。
  • simple_list_item_single_choice:都带有一个单选按纽。
  • simple_list_item_multiple:全部带有一个复选框。

3.CursorAdapter:它同样是BaseAdapter的子类,它为Cursor和ListView提供连接的桥梁。
  (1) newView():并不是每次都被调用,它只在实例化和数据增加时调用,而修改条目的内容时不会调用。
  (2) bindView():在绘制item之前或重绘时一定会调用。
  (3) changeCursor():类似于notifyDataSetChange()方法。

详细请参考附件中的实例

1.4、ListView优化

45dd588d-1506-45d2-8407-46b7446bea8a

BaseAdapter: 是经常用到的基础数据适配器,它的主要用途是将一组数据传到像ListView、Spinner、Gallery及GrideView等组件。
  (1) getCount():是listView的长度。
  (2) getView(): 根据这个长度逐一绘制它的每一行。
  (3) getItem()和getItemId()则需要处理和取得Adapter中的数据时调用。

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    View view = View.inflate(MainActivity.this, R.layout.item, null);
    TextView tvContent = (TextView) findViewById(R.id.tv_content);
    tvContent.setText(mDatas.get(position));
    return view;
}

第一种优化:复用对象

由于上方的代码每次需要一个View对象都会重新inflate一个view出来,没有实现对象的复用。

而系统给我们提供convertView,代表的是可复用的对象,当它为空则创建一个对象,否则直接复用。

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    View view;
    if(convertView == null){
        view = View.inflate(MainActivity.this, R.layout.item, null);
    }else{
        view = convertView;
    }
    TextView tvContent = (TextView) view.findViewById(R.id.tv_content);
    tvContent.setText(mDatas.get(position));
    return view;
}

第二种优化:减少查找次数

当converView为空时,会重新inflate一个View对象,除此之外还会findViewById进行查找工作,我们可以通过一个ViewHolder类来存储对应的

成员变量,然后通过getTag和setTag来操作,这时,当convertView为空时,只需要取出ViewHolder中存储的成员变量进行复用即可。

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    ViewHolder holder;
    if(convertView == null){
        convertView = View.inflate(MainActivity.this, R.layout.item, null);
        holder = new ViewHolder();
        holder.mTvContent = (TextView) convertView.findViewById(R.id.tv_content);
        convertView.setTag(holder);
    }else{
        holder = (ViewHolder) convertView.getTag();
    }
    holder.mTvContent.setText(mDatas.get(position));
    return convertView;
}

class ViewHolder{
    TextView mTvContent;
}

第三种写法:分批分页加载(待整理)

1.5、ListView多样式

  • 重写getViewTypeCount()   -- 该方法返回多个不同的布局总数。
  • 重写getItemViewType(int) -- 根据position返回响应的Item。
  • 根据view item的类型,在getView中创建正确的convertView。

a)创建MyAdapter继承BaseAdapter,首先在适配的getItemViewType()中通过计算得出不同的状态,用常量进行标记

public static final int TYPE_1 = 0;
public static final int TYPE_2 = 1;
public static final int TYPE_3 = 2;  
@Override
public int getItemViewType(int position) {
    int p = position % 6;
    if(p == 0){
        return TYPE_1;
    }else if (p < 3) {
        return TYPE_2;
    }else if (p < 6) {
        return TYPE_3;
    }else{
        return TYPE_1;
    }
}

注意:type必须从0开始,否则会报数组角标越界异常。

b)在getViewTypeCount()中获取不同布局的种类数

@Override
public int getViewTypeCount() {
    return 3;
}

c)此时我们需要给定义三个不同的布局,并创建三个不同的ViewHolder来针对不同的布局进行缓存复用

<TextView
    android:id="@+id/textview1"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@android:color/holo_green_dark"
    android:gravity="center"
    android:text="我是绿色的" />

其他三个布局都是如此,只是指定的背景颜色不一样,同时定义三个ViewHolder

class ViewHolder1{
    TextView textView;
}
class ViewHolder2{
    TextView textView;
}
class ViewHolder3{
    TextView textView;
}

d)此时我们在getView中来判断常量,进行填充不同的布局以及设置资源等操作

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    
    ViewHolder1 holder1 = null;
    ViewHolder2 holder2 = null;
    ViewHolder3 holder3 = null;
    
    int type = getItemViewType(position);
    if(convertView == null){
        // 按当前所需样式,确定new出的布局
        switch (type) {
        case TYPE_1:
            convertView = View.inflate(MainActivity.this, R.layout.item1, null);
            holder1 = new ViewHolder1();
            holder1.textView = (TextView) convertView.findViewById(R.id.textview1);
            convertView.setTag(holder1);
            break;
        case TYPE_2:
            convertView = View.inflate(MainActivity.this, R.layout.item2, null);
            holder2 = new ViewHolder2();
            holder2.textView = (TextView) convertView.findViewById(R.id.textview2);
            convertView.setTag(holder2);
            break;
        case TYPE_3:
            convertView = View.inflate(MainActivity.this, R.layout.item3, null);
            holder3 = new ViewHolder3();
            holder3.textView = (TextView) convertView.findViewById(R.id.textview3);
            convertView.setTag(holder3);
            break;
        }
    }else{
        switch (type) {
        case TYPE_1:
            holder1 = (ViewHolder1) convertView.getTag();
            break;
        case TYPE_2:
            holder2 = (ViewHolder2) convertView.getTag();
            break;
        case TYPE_3:
            holder3 = (ViewHolder3) convertView.getTag();
            break;
        }
    }
    
    // 根据不同样式设置资源
    switch (type) {
    case TYPE_1:
        holder1.textView.setText("我是绿色"+mDatas.get(position));
        break;
    case TYPE_2:
        holder2.textView.setText("我是蓝色"+mDatas.get(position));
        break;
    case TYPE_3:
        holder3.textView.setText("我是红色"+mDatas.get(position));
        break;
    }
    return convertView;
}

1.6、ListView的Item动画

a) 在ListView布局使用layoutAnimation属性引入一个动画文件。

<ListView 
    android:id="@+id/listview"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layoutAnimation="@anim/list_item_animation">
</ListView>

b) 在anin文件下创建该动画文件list_item_animation

<?xml version="1.0" encoding="utf-8"?>
<layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android"
    android:delay="0.2"
    android:animation="@anim/item_animation"
    android:animationOrder="normal"/>

android:delay的单位是s,每个Item出现的时间间隔

android:animation:表示每个Item对应的动画

android:animationOrder:动画执行顺序,normal从上到下;reverse从下到上;random随机

c) 动画文件又引入item_animation文件,该文件描述动画效果。

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate 
        android:fromXDelta="100%"
        android:fromYDelta="0"
        android:toXDelta="0"
        android:toYDelta="0"
        android:duration="1000"/>
    <alpha 
        android:fromAlpha="0"
        android:toAlpha="1"
        android:duration="1000"/>
    <rotate 
        android:fromDegrees="0"
        android:toDegrees="360"
        android:pivotX="50%"
        android:pivotY="50%"
        android:duration="1000"/>
</set>

d) 我们也可以在代码中来设置Item的加载动画

Animation animation = AnimationUtils.loadAnimation(this, R.anim.item_animation);
LayoutAnimationController animationController = new LayoutAnimationController(animation);
animationController.setDelay(0.4f);// 设置间隔时间
animationController.setOrder(LayoutAnimationController.ORDER_NORMAL);// 设置列表显示顺序
mListView.setLayoutAnimation(animationController);

1.7、ListView的焦点

android:descendantFocusability="blocksDescendants"

如题,在Item布局的根节点添加上述属性,android:descendantFocusability="blocksDescendants" 即可,另外该属性有三个可供选择的值:

  • beforeDescendants:viewgroup会优先其子类控件而获取到焦点
  • afterDescendants:viewgroup只有当其子类控件不需要获取焦点时才获取焦点
  • blocksDescendants:viewgroup会覆盖子类控件而直接获得焦点

例如:ListView的Item条目中的Button事件冲突解决。

  • 在ItemView配置的xml文件中的根节点添加属性android:descendantFocusability="blocksDescendants"
  • 在要添加事件的控件上添加android:focusable="false"
 2.1、ListView中使用CheckBox错位问题

思路:

  首先ListView在使用到CheckBox的时候会存在焦点问题,我们可以使用上方的方式处理该问题,其次是Item会被复用的问题,

我们可以通过在Bean中创建一个变量isChecked标记当前Item是否被选中的变量即可,当选中条目时就将isChecked设置为true,则可以解决错位问题。

a) 在MainActivity中创建ListView并创建适配器

public class MainActivity extends AppCompatActivity {

    private ListView mListView;
    private List<ItemBean> mDatas;
    private MyAdapter mAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mListView = (ListView) findViewById(R.id.lv_listview);

        // 模拟数据
        mDatas = new ArrayList<>();
        for (int i = 0; i< 100; i++) {
            ItemBean item = new ItemBean();
            item.setName("名字" + i);
            item.setAge("年龄" + i);
            mDatas.add(item);
        }
        mAdapter = new MyAdapter();
        mListView.setAdapter(mAdapter);

        // 条目点击事件
        mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                MyAdapter.ViewHold hold = (MyAdapter.ViewHold) view.getTag();
                ItemBean itemBean = mDatas.get(position);
                if(hold.cbClick.isChecked()) {
                    hold.cbClick.setChecked(false);
                    itemBean.setChecked(false);
                }else {
                    hold.cbClick.setChecked(true);
                    itemBean.setChecked(true);
                }
            }
        });
    }

    private class MyAdapter extends BaseAdapter {
        @Override
        public int getCount() {
            return mDatas.size();
        }

        @Override
        public Object getItem(int position) {
            return mDatas.get(position);
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            ViewHold hold;
            if (convertView == null) {
                convertView = View.inflate(MainActivity.this,R.layout.item_name_list, null);
                hold = new ViewHold();
                hold.cbClick = (CheckBox) convertView.findViewById(R.id.cb_check);
                hold.tvName = (TextView) convertView.findViewById(R.id.tv_name);
                hold.tvAge = (TextView) convertView.findViewById(R.id.tv_age);
                convertView.setTag(hold);
            }else {
                hold = (ViewHold) convertView.getTag();
            }
            ItemBean itemBean = mDatas.get(position);
            hold.cbClick.setChecked(itemBean.isChecked());
            hold.tvName.setText(itemBean.getName());
            hold.tvAge.setText(itemBean.getAge());
            return convertView;
        }

        class ViewHold {
            CheckBox cbClick;
            TextView tvName;
            TextView tvAge;
        }
    }
}

b) 然后是MainActivity的布局,Bean的字段为Name、Age和isCheckd。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="phoneserver.hll.com.myapplication.MainActivity">

    <ListView
        android:id="@+id/lv_listview"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    </ListView>

</RelativeLayout>

c) 最后是item的布局,这里需要处理焦点问题

<?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">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:descendantFocusability="blocksDescendants"
        android:padding="10dp">
        <CheckBox
            android:id="@+id/cb_check"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:focusable="false"/>

        <TextView
            android:id="@+id/tv_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerHorizontal="true"
            android:layout_centerVertical="true"
            android:text="名字"/>

        <TextView
            android:id="@+id/tv_age"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentRight="true"
            android:layout_centerVertical="true"
            android:text="年龄"/>

    </RelativeLayout>

</LinearLayout>

 

二、GridView

ListView是列表,GridView就是显示网格,他和ListView一样是AbsListView的子类,使用非常类似。

image

1.0、GridView网格线

前面我们知道ListView设置分割线是非常容易的,设置ListView的分割线颜色和宽度,只需要在布局中定义

android:divider和android:divideHeight属性即可。但是GridView并没有这样的方法。

a)其实实现这种效果并不难,原理是让每个item都设置成带有分割线的背景。

<?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:orientation="vertical" >
     <ScrollView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:fillViewport="true"
        android:scrollbars="none" >
        <com.finddreams.alipay.MyGridView
            android:id="@+id/gridview"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:horizontalSpacing="0.0dip"
            android:listSelector="@null"
            android:numColumns="2"
            android:scrollbars="none"
            android:stretchMode="columnWidth"
            android:verticalSpacing="0.0dip" />
    </ScrollView>
</LinearLayout>

b) 考虑到有时候Item会比较多的情况,一般用ScrollView嵌套起来。但是这样会导致只显示第一行。

产生问题的原因是因为GridView和ListView都是根据子item的宽高来显示大小的,而嵌套ScrollView中上下滑动就导致

系统无法正确的识别item的大小。下面是解决方案:

public class MyGridView extends GridView {
    public MyGridView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    public MyGridView(Context context) {
        super(context);
    }
    public MyGridView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }
    @Override
    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,
                MeasureSpec.AT_MOST);
        super.onMeasure(widthMeasureSpec, expandSpec);
    }
}

c) 定义一个selector,在里面设置形状外矩形rectangle,设置这个矩形的stroke描边属性的颜色为分割线的颜色,然后

在不同的state的item中设置不同的gradient渐变属性,从而实现单个item被点击选中时的效果。

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_pressed="true">
        <shape android:shape="rectangle">
            <gradient android:angle="270.0" android:endColor="#ffe8ecef" android:startColor="#ffe8ecef" />
            <stroke android:width="1.0px" android:color="@color/line" />
        </shape>
    </item>
    <item android:state_focused="true">
        <shape android:shape="rectangle">
            <gradient android:angle="270.0" android:endColor="#ffe8ecef" android:startColor="#ffe8ecef" />
            <stroke android:width="1.0px" android:color="@color/line" />
        </shape>
    </item>
    <item>
        <shape android:shape="rectangle">
            <gradient android:angle="270.0" android:endColor="#ffffffff" android:startColor="#ffffffff" />
            <stroke android:width="1.0px" android:color="@color/line" />
        </shape>
    </item>
</selector>

d) 给 GridView的item布局使用上面定义的selector

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:layout_margin="0.0dip"
    android:background="@color/griditems_bg" >
    <RelativeLayout
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_centerInParent="true"
        android:background="@drawable/bg_gv"
        android:padding="12.0dip" >
        <ImageView
            android:id="@+id/iv_item"
            android:layout_width="58.0dip"
            android:layout_height="58.0dip"
            android:layout_centerHorizontal="true"
            android:contentDescription="@string/app_name" />
        <TextView
            android:id="@+id/tv_item"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@id/iv_item"
            android:layout_centerHorizontal="true"
            android:layout_marginTop="5.0dip"
            android:maxLines="1"
            android:textColor="@color/commo_text_color"
            android:textSize="14.0sp"/>
    </RelativeLayout>
</RelativeLayout>

e) 之后是适配器的编写,比较简单则不详细叙述

public class MyGridAdapter extends BaseAdapter {
    private Context mContext;
    public String[] strs = { "转账", "余额宝", "手机充值", "信用卡还款", "淘宝电影", "彩票",
            "当面付", "亲密付", "机票", };
    public int[] imgs = { R.drawable.app_transfer, R.drawable.app_fund,
            R.drawable.app_phonecharge, R.drawable.app_creditcard,
            R.drawable.app_movie, R.drawable.app_lottery,
            R.drawable.app_facepay, R.drawable.app_close, R.drawable.app_plane };
    public MyGridAdapter(Context mContext) {
        super();
        this.mContext = mContext;
    }
    @Override
    public int getCount() {
        return strs.length;
    }
    @Override
    public Object getItem(int position) {
        return position;
    }
    @Override
    public long getItemId(int position) {
        return position;
    }
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        if (convertView == null) {
            convertView = View.inflate(mContext, R.layout.grid_item, null);
        }
        TextView tv = BaseViewHolder.get(convertView, R.id.tv_item);
        ImageView iv = BaseViewHolder.get(convertView, R.id.iv_item);
        iv.setBackgroundResource(imgs[position]);
        tv.setText(strs[position]);
        return convertView;
    }
}

f) 比较重要的是下面的这个万能ViewHolder的写法,详细demo参考附件GridView网格

public class BaseViewHolder {
    @SuppressWarnings("unchecked")
    public static <T extends View> T get(View view, int id) {
        SparseArray<View> sparseArray = (SparseArray<View>) view.getTag();
        if (sparseArray == null) {
            sparseArray = new SparseArray<View>();
            view.setTag(sparseArray);
        }
        View childView = sparseArray.get(id);
        if (childView == null) {
            childView = view.findViewById(id);
            sparseArray.put(id, childView);
        }
        return (T) childView;
    }
}

 

开源控件下载地址:

链接:http://pan.baidu.com/s/1pLs0n2r 密码:hnyv

转载于:https://www.cnblogs.com/pengjingya/p/5508998.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值