针对多种屏幕进行设计

前言

从前写项目的时候都是看着我的真机的样子写,写合适我的真机的布局,放适合他的图片啊,心里一直不敢把它放到其他的真机上实验。有一天我鼓起勇气,把它放到其他的手机上运行,哎呀,这是什么啊。完全不是我理想中的样子。

而且这还只是手机,我还没有在平板,电脑,电视,wear上看。。不用想也知道,结局肯定惨不忍睹。有幸今天看到了Google这篇针对多种屏幕进行设计的教程,我学到了很多以前根本没接触过的东西。

正文

Android支持多种不同屏幕的设备,手机,平板,电视,手表,所以要让APP兼容不同设备还是很中澳的一件事。但是单是兼容肯定还是远远不够,还要能针对不同的屏幕作出优化,这样才能提升用户的体验。

这次分为三个内容:

  • 支持各种屏幕尺寸
    • 使用灵活的视图尺寸、 RelativeLayout、屏幕尺寸和屏幕方向限定符、别名过滤器以及自动拉伸位图
  • 支持各种屏幕密度
    • 支持具有不同像素密度的屏幕
  • 实施自适应用户界面流程
    • 运行时对当前布局的检测,根据当前布局做出响应,处理屏幕配置变化

支持各种屏幕尺寸

使用“wrap_content”和“match_parent”

要保证布局的灵活性,我们避不开适用这两个。

wrap_content 是能包容下view自身内容的最小尺寸。
match_parent 是延伸view自身去匹配父布局的宽度。

使用它们的值而不是硬编码,view就可以用它们自己的尺寸或填充父布局的尺寸:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <LinearLayout android:layout_width="match_parent" 
                  android:id="@+id/linearLayout1"  
                  android:gravity="center"
                  android:layout_height="50dp">
        <ImageView android:id="@+id/imageView1" 
                   android:layout_height="wrap_content"
                   android:layout_width="wrap_content"
                   android:src="@drawable/logo"
                   android:paddingRight="30dp"
                   android:layout_gravity="left"
                   android:layout_weight="0" />
        <View android:layout_height="wrap_content" 
              android:id="@+id/view1"
              android:layout_width="wrap_content"
              android:layout_weight="1" />
        <Button android:id="@+id/categorybutton"
                android:background="@drawable/button_bg"
                android:layout_height="match_parent"
                android:layout_weight="0"
                android:layout_width="120dp"
                style="@style/CategoryButtonStyle"/>
    </LinearLayout>

    <fragment android:id="@+id/headlines" 
              android:layout_height="fill_parent"
              android:name="com.example.android.newsreader.HeadlinesFragment"
              android:layout_width="match_parent" />
</LinearLayout>

此视图在纵向模式和横向模式下的显示效果如下所示。请注意,组件的尺寸会自动适应屏幕的高度和宽度:

这里写图片描述

使用相对布局

你可以使用LinearLayout去构造复杂布局,但LinearLayout只是将view都直接排列出来,如果不想用直线的方式而换用view之间或view和父布局之间的相对关系,那么RelativeLayout是不错的选择,下面是官方示例:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
        android:id="@+id/label"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Type here:"/>
    <EditText
        android:id="@+id/entry"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/label"/>
    <Button
        android:id="@+id/ok"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/entry"
        android:layout_alignParentRight="true"
        android:layout_marginLeft="10dp"
        android:text="OK" />
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toLeftOf="@id/ok"
        android:layout_alignTop="@id/ok"
        android:text="Cancel" />
</RelativeLayout>

小屏幕的截图

大屏幕的截图

虽然组件的尺寸有所变化,但它们的空间关系仍会保留。

使用尺寸限定符

灵活的布局优势有限,如果我们要支持更多的设备,给使用不同的设备的用户带来更好的用户体验,我们必须要准备一些针对不同屏幕尺寸的备用布局。这时我们要使用配置限定符,这样系统就能在运行的时候自动选择合适的资源了(根据不同的设备去加载不同的布局文件)。

比如很多应用会在大屏幕的设备上实现“双平板模式”(一遍显示标题,另一边显示内容),在平板和电视上可以这么做,但是在手机上就不适合了,所以我们要额外准备文件来应对这种情况:

res/layout/main.xml,单面板(默认)布局:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <fragment android:id="@+id/headlines"
              android:layout_height="fill_parent"
              android:name="com.example.android.newsreader.HeadlinesFragment"
              android:layout_width="match_parent" />
</LinearLayout>

res/layout-large/main.xml,双面板布局:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="horizontal">
    <fragment android:id="@+id/headlines"
              android:layout_height="fill_parent"
              android:name="com.example.android.newsreader.HeadlinesFragment"
              android:layout_width="400dp"
              android:layout_marginRight="10dp"/>
    <fragment android:id="@+id/article"
              android:layout_height="fill_parent"
              android:name="com.example.android.newsreader.ArticleFragment"
              android:layout_width="fill_parent" />
</LinearLayout>

注意到第二个布局文件中文件目录上有large限定符,系统会在较大屏幕上选择该布局,而在小屏幕设备上选择其他布局。

使用最小宽度限定符

在Android版本是3.2之前的设备上,即使设备屏幕大小属于large级别,但是很多应用还是会根据不同的屏幕大小去显示不同的布局(比如说七英寸和十英寸的平板就要用不同的布局),这是最小宽度限定符产生的原因。

最小宽度限定符能让你指定某个最小宽度(单位是dp)来定位屏幕。比如说标准 7 英寸平板电脑的最小宽度为 600 dp,因此如果您要在此类屏幕上的用户界面中使用双面板(但在较小的屏幕上只显示列表),您可以使用上文中所述的单面板和双面板这两种布局,但您应使用 sw600dp 指明双面板布局仅适用于最小宽度为 600 dp 的屏幕,而不是使用 large 尺寸限定符:

res/layout/main.xml,单面板(默认)布局:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <fragment android:id="@+id/headlines"
              android:layout_height="fill_parent"
              android:name="com.example.android.newsreader.HeadlinesFragment"
              android:layout_width="match_parent" />
</LinearLayout>

res/layout-sw600dp/main.xml,双面板布局:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="horizontal">
    <fragment android:id="@+id/headlines"
              android:layout_height="fill_parent"
              android:name="com.example.android.newsreader.HeadlinesFragment"
              android:layout_width="400dp"
              android:layout_marginRight="10dp"/>
    <fragment android:id="@+id/article"
              android:layout_height="fill_parent"
              android:name="com.example.android.newsreader.ArticleFragment"
              android:layout_width="fill_parent" />
</LinearLayout>

也就是说在系统屏幕大于等于600时,系统会加载layout_sw600dp/main.xml布局,而在手机上会加载layout/main.xml布局。

这个时候问题来了,最小宽度限定符是在Android3.2版本的时候引进的,在3.2版本之前,系统识别不出来sw600dp,只认识large,所以我们不得不去在准备一个尺寸限定符是large的布局文件,而这个布局文件和sw600dp的布局文件的内容完全一样。重复的文件会引起维护的困难,下面会提到如何解决这一问题。

使用布局别名

最小宽度限定符只适用于Android3.2及更高版本,所以我们仍需考虑兼容更低版本的问题。比如我们在手机上显示一个页面,但是在平板和电视上可以显示双面板,我们应该添加如下布局文件:

  • res/layout/main.xml: 单面板布局
  • res/layout-large: 多面板布局
  • res/layout-sw600dp: 多面板布局

后两个文件是相同的,因为其中一个用于和 Android 3.2 设备匹配,而另一个则是为使用较低版本 Android 的平板电脑和电视准备的。

为了避免重复带来维护问题,我们用别名的办法来解决问题。我们定义这两个布局:

  • res/layout/main.xml,单面板布局
  • res/layout/main_twopanes.xml,双面板布局

然后添加这两个文件:

  • res/values-large/layout.xml:
<resources>
    <item name="main" type="layout">@layout/main_twopanes</item>
</resources>
  • res/values-sw600dp/layout.xml:
<resources>
    <item name="main" type="layout">@layout/main_twopanes</item>
</resources>

后面两个的布局我们通过选择器的方式制定设备该加载的布局,Android版本3.2之前认识large,加载了main-twopanes.xml,Android3.2之后的识别到sw600dp,也加载相同的布局。这样通过选择器避开重复定义布局,但都指向了相同的布局,达到了只用定义一个布局的目的。

使用方向限定符

某些布局同时支持横向和纵向显示,但是我们仍然可以通过调整来进一步优化用户体验。官方给出了下面的示例:

  • 小屏幕,纵向:单面板,带徽标
  • 小屏幕,横向:单面板,带徽标
  • 7 英寸平板电脑,纵向:单面板,带操作栏
  • 7 英寸平板电脑,横向:双面板,宽,带操作栏
  • 10 英寸平板电脑,纵向:双面板,窄,带操作栏
  • 10 英寸平板电脑,横向:双面板,宽,带操作栏
  • 电视,横向:双面板,宽,带操作栏

这些布局都定义在res/layout的某个xml文件里,我们可以使用定义多个布局,为了避免重复我们应该用别名来匹配。

res/layout/onepane.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <fragment android:id="@+id/headlines"
              android:layout_height="fill_parent"
              android:name="com.example.android.newsreader.HeadlinesFragment"
              android:layout_width="match_parent" />
</LinearLayout>

res/layout/onepane_with_bar.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <LinearLayout android:layout_width="match_parent" 
                  android:id="@+id/linearLayout1"  
                  android:gravity="center"
                  android:layout_height="50dp">
        <ImageView android:id="@+id/imageView1" 
                   android:layout_height="wrap_content"
                   android:layout_width="wrap_content"
                   android:src="@drawable/logo"
                   android:paddingRight="30dp"
                   android:layout_gravity="left"
                   android:layout_weight="0" />
        <View android:layout_height="wrap_content" 
              android:id="@+id/view1"
              android:layout_width="wrap_content"
              android:layout_weight="1" />
        <Button android:id="@+id/categorybutton"
                android:background="@drawable/button_bg"
                android:layout_height="match_parent"
                android:layout_weight="0"
                android:layout_width="120dp"
                style="@style/CategoryButtonStyle"/>
    </LinearLayout>

    <fragment android:id="@+id/headlines" 
              android:layout_height="fill_parent"
              android:name="com.example.android.newsreader.HeadlinesFragment"
              android:layout_width="match_parent" />
</LinearLayout>

res/layout/twopanes.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="horizontal">
    <fragment android:id="@+id/headlines"
              android:layout_height="fill_parent"
              android:name="com.example.android.newsreader.HeadlinesFragment"
              android:layout_width="400dp"
              android:layout_marginRight="10dp"/>
    <fragment android:id="@+id/article"
              android:layout_height="fill_parent"
              android:name="com.example.android.newsreader.ArticleFragment"
              android:layout_width="fill_parent" />
</LinearLayout>

res/layout/twopanes_narrow.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="horizontal">
    <fragment android:id="@+id/headlines"
              android:layout_height="fill_parent"
              android:name="com.example.android.newsreader.HeadlinesFragment"
              android:layout_width="200dp"
              android:layout_marginRight="10dp"/>
    <fragment android:id="@+id/article"
              android:layout_height="fill_parent"
              android:name="com.example.android.newsreader.ArticleFragment"
              android:layout_width="fill_parent" />
</LinearLayout>

定义好所有可能的布局之后,我们用配置限定符正确映射不同屏幕设配要加载的资源。还是利用别名就能解决。

res/values/layouts.xml:

<resources>
    <item name="main_layout" type="layout">@layout/onepane_with_bar</item>
    <bool name="has_two_panes">false</bool>
</resources>

res/values-sw600dp-land/layouts.xml:

<resources>
    <item name="main_layout" type="layout">@layout/twopanes</item>
    <bool name="has_two_panes">true</bool>
</resources>

res/values-sw600dp-port/layouts.xml:

<resources>
    <item name="main_layout" type="layout">@layout/onepane</item>
    <bool name="has_two_panes">false</bool>
</resources>

res/values-large-land/layouts.xml:

<resources>
    <item name="main_layout" type="layout">@layout/twopanes</item>
    <bool name="has_two_panes">true</bool>
</resources>

res/values-large-port/layouts.xml:

<resources>
    <item name="main_layout" type="layout">@layout/twopanes_narrow</item>
    <bool name="has_two_panes">true</bool>
</resources>
使用自动拉伸位图

完成的布局的准备,千万别忘了图片。如果我们在不同大小的设备上使用简单的图片,会发现效果很不理想。因为系统运行的时候会自动的拉伸或是收缩图片。解决的方案是使用自动拉伸位图,这是一种特使格式的PNG图片,其中会指明可以拉伸以及不可以拉伸的区域。

因此,如果设计的是用于尺寸可变的组件上的位图,请务必使用自动拉伸技术。要将某个位图转换成自动拉伸位图,先准备好位图,下面是官方示例:

这里写图片描述

然后通过 SDK 的 draw9patch 实用工具(位于 tools/ 目录中)运行该图片,在该工具中绘制像素以标出要拉伸的区域以及左侧和顶部的边界,还可以沿右侧和底部边界绘制像素以标出用于放置内容的区域

这里写图片描述

请注意沿边界显示的黑色像素。顶部和左侧边界上的像素用于指定可以拉伸的图片区域,右侧和底部边界上的像素则用于指定放置内容的区域。

另请注意 .9.png 的扩展名。因为系统框架需要通过此扩展名确定相关图片是自动拉伸位图,而不是普通 PNG 图片。

如果您将此背景应用到某个组件(通过设置 android:background=”@drawable/button”),系统框架就会正确拉伸图片以适应按钮的尺寸。

这里写图片描述


支持各种屏幕密度

使用非密度制约像素

我们一定要避免使用绝对布局来定义布局或尺寸,因为在不同屏幕密度的设备上运行起来布局尺寸天差地别。所以务必使用dp或sp单位定义尺寸。dp是一种非密度制约像素,其大小和160dpi像素大小相同,sp也是一种基本单位,但他可根据用户的偏好文字大小进行调整,我们应该使用该单位来定义文字大小,不用用它来定义布局啊。

例如,请使用 dp(而非 px)指定两个视图间的间距:

<Button android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/clickme"
    android:layout_marginTop="20dp" />

请务必使用 sp 指定文字大小:

<TextView android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:textSize="20sp" />
提供备用位图

Android可以在不同屏幕尺寸的设备上运行,所以我们应该提供满足普片密度范围的要求:低密度,中密度,高密度以及超高密度,这有助于图片在不同设备上都得到出色的质量和效果。

要提供这些资源,应该先提取元素图片,然后再根据以下尺寸范围为各密度生成图片:

  • xhdpi:2.0
  • hdpi:1.5
  • mdpi:1.0(最低要求)
  • ldpi:0.75

也就是说如果你为xhdpi设备生成了一张200*200尺寸的图片,你要为hdpi提供150*150尺寸的图片,mdpi是100*100,ldpi是75*75。

然后把图片方法相应的子目录下,然后系统会根据设备的屏幕密度去自动选择要加载的图片:

MyProject/
  res/
    drawable-xhdpi/
        awesomeimage.png
    drawable-hdpi/
        awesomeimage.png
    drawable-mdpi/
        awesomeimage.png
    drawable-ldpi/
        awesomeimage.png

实施自适应用户界面流程

运行的时候,设备屏幕大小不同加载的布局文件也不同,这是我们要根据加载的布局来给出相应的响应。

确定当前布局

要确定相应布局的实施,当然要知道现在加载的哪种布局。比如我们想知道用户现在是出于单面板模式还是双面板模式,要做到这一点,我们可以查询视图是否存现以及视图有没有已经显示出来。

public class NewsReaderActivity extends FragmentActivity {
    boolean mIsDualPane;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main_layout);

        View articleView = findViewById(R.id.article);
        mIsDualPane = articleView != null &&
                        articleView.getVisibility() == View.VISIBLE;
    }
}

再举一个适应各种组件的存在情况的方法示例:在对这些组件执行操作前先查看它们是否可用。新闻阅读器示例应用中有一个用于打开菜单的按钮,但只有在版本低于 3.0 的 Android 上运行该应用时,这个按钮才会存在,因为 API 级别 11 或更高级别中的 ActionBar 已取代了该按钮的功能。因此,可以使用以下代码为此按钮添加事件侦听器:

Button catButton = (Button) findViewById(R.id.categorybutton);
OnClickListener listener = /* create your listener here */;
if (catButton != null) {
    catButton.setOnClickListener(listener);
}
根据当前的布局作出响应

因为布局的不同一些操作会产生不同的结果。比如双面板模式,点击点击左侧标题,右侧就能显示文章,而在单面板模式里,就要启动一个独立的Activity或是替换fragment来实现:

@Override
public void onHeadlineSelected(int index) {
    mArtIndex = index;
    if (mIsDualPane) {
        /* display article on the right pane */
        mArticleFragment.displayArticle(mCurrentCat.getArticle(index));
    } else {
        /* start a separate activity */
        Intent intent = new Intent(this, ArticleActivity.class);
        intent.putExtra("catIndex", mCatIndex);
        intent.putExtra("artIndex", index);
        startActivity(intent);
    }
}

同样,如果该应用处于双面板模式下,就应设置带导航标签的操作栏;但如果该应用处于单面板模式下,就应使用旋转窗口小部件设置导航栏。

final String CATEGORIES[] = { "热门报道", "政治", "经济", "Technology" };

public void onCreate(Bundle savedInstanceState) {
    ....
    if (mIsDualPane) {
        /* use tabs for navigation */
        actionBar.setNavigationMode(android.app.ActionBar.NAVIGATION_MODE_TABS);
        int i;
        for (i = 0; i < CATEGORIES.length; i++) {
            actionBar.addTab(actionBar.newTab().setText(
                CATEGORIES[i]).setTabListener(handler));
        }
        actionBar.setSelectedNavigationItem(selTab);
    }
    else {
        /* use list navigation (spinner) */
        actionBar.setNavigationMode(android.app.ActionBar.NAVIGATION_MODE_LIST);
        SpinnerAdapter adap = new ArrayAdapter(this,
                R.layout.headline_item, CATEGORIES);
        actionBar.setListNavigationCallbacks(adap, handler);
    }
}
重复使用其他Avtivity中的代码片段

多屏设计的重复模式是指,对于大屏幕的配置来说,已实施界面的一部分会作为面板,而对于小屏幕设备,这部分可能就是一个独立的Activity。但是因为他们的内容都一样,所以我们应该复用同样的代码片段来避免重复,通常都是复用Fragment来做到这一点。比如用于显示新闻内容的ArticleFragment,在双面板里作为一部分:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="horizontal">
    <fragment android:id="@+id/headlines"
              android:layout_height="fill_parent"
              android:name="com.example.android.newsreader.HeadlinesFragment"
              android:layout_width="400dp"
              android:layout_marginRight="10dp"/>
    <fragment android:id="@+id/article"
              android:layout_height="fill_parent"
              android:name="com.example.android.newsreader.ArticleFragment"
              android:layout_width="fill_parent" />
</LinearLayout>

在小屏幕设备的Activity里(无需布局)又重复使用了它:

ArticleFragment frag = new ArticleFragment();
getSupportFragmentManager().beginTransaction().add(android.R.id.content, frag).commit();

当然这个XML文件里面定义这个Fragemnt的效果是相同的,但是我们没有必要去定义,因为这个Fragement是这个Activity里的唯一组件。

当然了,我们在设计Fragment的时候应该要注意不要把它和Activity之间设计的耦合性太强,分都分不开是不好设计。我们只要在Fragment里面设计好它和Activity之间的交互方式,然后去实施它就好了:
比如定义一个接口,而不是直接嵌套在Activity里面:

public class HeadlinesFragment extends ListFragment {
    ...
    OnHeadlineSelectedListener mHeadlineSelectedListener = null;

    /* Must be implemented by host activity */
    public interface OnHeadlineSelectedListener {
        public void onHeadlineSelected(int index);
    }
    ...

    public void setOnHeadlineSelectedListener(OnHeadlineSelectedListener listener) {
        mHeadlineSelectedListener = listener;
    }
}

然后,如果用户选择某个标题,相关Fragment就会通知由主Activity指定的侦听器(而不是通知某个硬编码的具体Activity):

public class HeadlinesFragment extends ListFragment {
    ...
    @Override
    public void onItemClick(AdapterView<?> parent,
                            View view, int position, long id) {
        if (null != mHeadlineSelectedListener) {
            mHeadlineSelectedListener.onHeadlineSelected(position);
        }
    }
    ...
}
处理屏幕配置的变化

如果我们使用单独的Activity作为界面的实施部分,可能要考虑这方面问题。意思是竖直方向上我们是单独的Activity来解决问题的,但是横向显示的时候就转换成双面板模式。这个问题在平板上考虑的更多。

例如,在运行 Android 3.0 或更高版本的标准 7 英寸平板电脑上,如果新闻阅读器示例应用运行在纵向模式下,就会在使用独立活动显示新闻报道;但如果该应用运行在横向模式下,就会使用双面板布局。

如果用户处于纵向模式下且屏幕上显示的是用于阅读报道的活动,那么我们就需要在检测到屏幕方向变化(变成横向模式)后执行相应操作,即停止上述活动并返回主活动,以便在双面板布局中显示相关内容:

public class ArticleActivity extends FragmentActivity {
    int mCatIndex, mArtIndex;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mCatIndex = getIntent().getExtras().getInt("catIndex", 0);
        mArtIndex = getIntent().getExtras().getInt("artIndex", 0);

        // If should be in two-pane mode, finish to return to main activity
        if (getResources().getBoolean(R.bool.has_two_panes)) {
            finish();
            return;
        }
        ...
}

ps : 中间我查了查drawable和mipmap文件的区别,大家说啥的都由,官方一句概括,应用LOGO在放在mipmap里,提高渲染速度,减少CPU压力,其他的还是放在drawable里和原来一样。我也不太懂,但我是都放在mipmap里的。

ps:如果大家对.9.png的制作还有一点,可以看这一篇

好了,今天的内容就是这么多了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值