首页

如何设计交互缺省页?

资深UI设计者

缺省页指页面的信息内容为空或信息响应异常的状态;设计缺省状态的作用不仅是引导用户在异常边界状态的操作提示,同时也是安抚用户体验情绪的重要场景;更重要的是为边界场景营造出良好用户体验。通过分析缺省状态产生的原理,从而更为准确地把握交互缺省页的设计原则。

哪些状态需要缺省页

谈到缺省页面可能是设计师最容易忽略输出的范围,可能直到对接的开发同学提出来,「这个页面,如果没有数据的时候,该怎么显示啊?」。为了更好地把控设计缺省页交互状态,首先要了解缺省页出现的原理。App 页面内容(包括图片、文字、数据字段等等)都是请求服务器数据,顺利返回后,正常显示到客户端页面。在了解清楚基础实现逻辑后,就可以开始梳理、整理缺省状态的设计思路。

△ 图1 缺省状态的场景梳理图

缺省状态包括:系统层、信息层、空白层。

系统层:指当用户请求服务器时,返回提示请求提交失败,并检测到失败原因时呈现的页面;例如:加载失败、服务器异常、无网络等;页面一般会有重新请求的快捷按钮。文案上可做失败原因的细分描述,也可节约成本使用网络异常的统一文案。

信息层:请求服务器数据成功,但返回的数据异常的页面;例如:内容已删除、内容已下架、内容不存在;文案内容以提示数据类型的缺失为主。显示形式除了常有的全屏缺省图,还会出现在数据列表下单一内容缺失的缺省模块化的情况,例如:单一作品在书架上显示已下架。

空白层:请求服务器数据成功,但显示无数据;内容页在无数据时需要缺省状态进行表达;例如:页面空数据、搜索无结果等。空白页面属于正常网络显示场景,所以一般会在缺省页附带有相似属性模块的用户引导,争取用户重复消费的目标,满足用户的操作的诉求。

最后根据每个不同的缺省状态,梳理产品相对应的场景。逐一根据场景特点来设计页面内容。那缺省页的设计有哪些表现形式呢?

缺省页的表现形式

没有用心设计的缺省页无法给用户带来良好用户体验,并可能给用户带来困扰,如下图:某小众直播平台的拉新邀请页面,无邀请记录状态下没有任何有效反馈信息,用户不能明确得知到底是网络问题还是账号同步出错,亦或者是没有一次邀请。正确的缺省页设计内容理应明确表达出符合用户心理预期的视觉场景表达(图形);和使用易理解和语法恰当地表达当前的异常状态(标题)甚至于引导用户解决问题的文案描述。

△ 图5 缺省页的错误示范

1. 视觉图案+文案

此类缺省设计形式一般应用于表达系统性无响应或初始空白态的缺省场景。视觉图案一般使用 app 吉祥物或主色调延展出的 icon 或插画来表示缺省状态;文字:通常为「标题」或「标题+描述」结构;标题通常是表达出现缺省的原因;描述文案则说明结束缺省状态的解决办法,如「请检查网络是否顺畅」 等等。

2. 视觉图案+文案+引导

此类缺省设计形式一般运用于需要用户引导操作来达到业务目标的缺省场景。在视觉图案+文案的基础上加入引导模块,主要作用于避免用户在数据边界的状态下,会因为无法达到操作目的而提高的跳出率。引导模块的内容包括:相似属性内容,相似行为目标按钮或解决缺省状态操作按钮,加入引导,用户进行某项行为或者感知某些信息,对于功能的教学和使用频率的提升有着重要作用。引导模块的形式也是日新月异,逐渐变成新用户业务引导的作用,不仅限于页面平铺,也可以做成固定气泡微动效,例如:抖音的发布缺省页。

缺省页的设计技巧

缺省页除了常规的提示型设计方法,还有许多其他的设计技巧,帮助用户在遇到困难,更好地安抚用户的情绪。这些设计技巧有些是替代原来的缺省内容,让用户有更多的消费空间与深度。有些是拓展缺省状态的补充内容,让用户不容易跳出页面,增加用户的消费时长。具体如下:

1. 使用推荐内容

缺省状态中的空白层非常影响边界情况的用户体验,提出一种假设,是否可以刻意推荐相同属性的内容呢?这样的界面既不会显得苍白无力又可以留住用户的注意力。相似性的内容也可以解决用户目标的迫切性。所以说,这种方法非常适合内容型产品中使用。例如:新用户在打开电商产品的购物车时候,理应是空白无消费行为的操作记录。那么平台方通过用户画像与热门排行算法推荐了一个商品流。这样可以解决用户无目标性挑选的诉求,增加消费时长。至于产品如果确定用户画像的推荐算法,可以通过获取第三方登录的个人基本数据之后,才给我推荐了数据库内相对应标签的热门商品,这样推荐的精准度也会高些。

2. 使用缓存

是否使用缓存内容代替缺省状态?根据产品特性来判断,工具类、金融类等同类型产品不适合使用缓存;因为用户交互操作的数据必须保持实时性与真实性。而内容型、电商类等类型产品适合使用缓存来代替缺省状态;理由:用户消费内容的转化路径是先消费后转化的行为特点,不存在系统操作门槛,且缓存内容可以代替产品的缺省状态,安抚用户操作失败所带来跳出率过高的风险。

3. 情感化表达

当缺省页给到用户时,通常省时省力的做法就是老老实实告诉用户当前的状态,最多配上一个具有通识性的灰色 icon。但是,秉持着以用户体验为己任的时代,我们其实可以把缺省内容表达得更加生动形象一些。在这里会加入一些情感化的表达,而不是仅仅只是做到准确的目标而已,比如加上活泼的插图故事,或者把文案写得更加拟人化、戏剧化一些。这些配图在让用户明白当前的状态的同时,往往也能引发用户会心一笑,从而弥补空白页面带来的失落感甚至可以带给用户一些正面的情感。如下图:

4. 提供新任务

通常缺省页的引导模块都着眼于解决当前任务。如果碰到没有解决方案的情况(例如:404,服务器崩溃等)可以提供给用户具有情感共情的新任务,让他们暂时忘记无法达到目标的挫败感,又有体谅的情怀。帮助建立正向积极的品牌价值观。例如:访问腾讯网时访问失败的时候,网页除了显示 404 状态之外,还会显示腾讯「宝贝回家」的公益寻人计划。将缺省页与公益内容相结合,不仅改善到用户缺省状态。也贯彻腾讯价值观「用户为本,科技向善」的输出。一个好的缺省页也可以承担社会责任,让公益传播到每个角落。

△ 图10 腾讯网404公益任务缺省页

结语:作为设计师有时会听到需求方表述「这种极少出现的情况,我们可以暂且不管它。」但是细节见真章,所有优秀的体验设计都必须照顾到方方面面的缺省情况。让每个用户的流量价值发挥到最大,产生相互信任的良好的品牌关系。这样的平台生态是良性的,这样的产品会更有流量转化的商业化价值。

文章来源:优设    作者:腾讯动漫TCD

uni-app uni.request接口封装

seo达人

uni-app uni.request接口封装

今天在做uni-app项目时,发现在uni-app中 调取后台接口需要大量的重复编辑,就在想能不能封装一个如同Vue项目中的this.$axios.get(url,data).then();格式,这样就减少了很多代码重复!!

封装为如同this.$axios.get(url,data).then();格式

第一步、

我们先在index首页中的组件部分,创建一个js文件;





第二步、

我们在uni-app的入口文件中引入request.js文件;

在入口文件中挂载到uni-app实例上;





第三步、

开始接口封装:

(以下为js文件代码)



//先把接口暴露出去

export default{

//我们先定一个uni-app方法 以便于以下操作使用uni-app调取接口时便利

request(options){

///我们使用Promise方法来实现调用接口时后面多个.then()的方法

//只有Promise能实现如同$axios后面连续多个.then()的方法

return new Promise((reslove,reject)=>{

uni.request({

...options,

success:res=>{

//判断我们在使用封装的自定义时第三个参数是否为native

//当native为true时 我们返回原数据

if(options.native){

reslove(res)

}

//当native为false时 我们直接返回data中的数据

if(res.statusCode === 200){

reslove(res.data)

}else{

//加入接口参数错误或接口地址错误时 我们返回原错误提示

reject(res)

}

}

})

})

},

//在方法中 第二个参数和第三个参数用ES6新语法来添加默认值

//接口调取get方法

get(url,data={},options={}){

//我们把传过来的参数赋给options,这样我们在使用uni-app

//this.request()方法时 传递一个参数就可以

options.url = url;

options.data = data;

options.method = 'get';

//调用上面自己定义的this.request()方法传递参数

return this.request(options)

},

//接口调取post方法

post(url,data={},options={}){

options.url = url;

options.data = data;

options.method = 'post';

return this.request(options)

}

}



这样我们就已经封装完成啦,接下来就是 在页面内使用!

第四步、

我们可以在页面中来调取已经封装好的自定义事件啦



例一:

个人建议使用ES6新语法 箭头函数 不然使用this还要重新在外面声明定义,太麻烦了,使用箭头函数就会方便很多



// 已封装好的接口方法

//本案例调取接口时 没有参数上传 直接调用的

//这样使用方法时只传递了一个参数,也就是接口地址

//第二个参数没有写,默认为空;假如有参数的话 可以直接填写

//后面的参数都为接口内已经定义好的默认值:{}空对象

//里面的res为接口返回数据中的data里面的内容

this.$H.get('/api/getIndexCarousel.jsp').then(res=>{

//res打印出来是接口返回数据中data里面的数据

console.log(res)

//赋给数据区的变量,方便本页面使用

this.swiperData = res

});



例二、



// 已封装好的接口方法

//本案例使用时 传递了三个参数

//第一个为:接口地址

//第二个为:调取接口传递的参数,方法使用时不用传参,写空对象就好

//第三个为:自定义事件中 native 的属性 若为true 则返回原数据

//若想返回原数据,必须要填写第二个参数,若没有参数,也要写空对象

//因为方法调用时 是按照传参顺序调用的,若不写 参数传递就会出错

this.$H.get('/api/getIndexCarousel.jsp',{},{

native:true

}).then(res=>{

//res打印出来的数据是接口返回来的原数据

console.log(res)

//赋给数据区的变量,方便本页面使用

this.swiperData = res

});




每天学习一个Android中的常用框架——1.Litepal

seo达人

文章目录

1.简介

2.特性

3.演示

3.1 集成

3.2 配置

3.3 创建数据库

3.4 升级数据库

3.5 插入数据

3.6 查询数据

3.7 更新数据

3.8 删除数据

4.版本异同

5.源码地址

1.简介

Litepal——作为带我入行的第一本教学书籍《Android第一行代码》的作者郭霖老师所写出来的持久化框架,几乎算是我接触Android世界之后第一个遇到的框架,故将该框架列为一系列学习框架博客的首位。

根据Litepal的GitHub主页:Litepal,可以看到该框架的一些简介:



LitePal is an open source Android library that allows developers to use SQLite database extremely easy. You can finish most of the database operations without writing even a SQL statement, including create or upgrade tables, crud operations, aggregate functions, etc. The setup of LitePal is quite simple as well, you can integrate it into your project in less than 5 minutes.



事实上,正如这段简介所说,集成Litepal相当简单,不需要超过五分钟时间。使用Litepal,也适合对sql语言还不熟悉的开发者快速上手。



2.特性

让我们继续浏览Litepal的GitHub主页,可以发掘Litepal的一些特性:



Using object-relational mapping (ORM) pattern.

Almost zero-configuration(only one configuration file with few properties).

Maintains all tables automatically(e.g. create, alter or drop tables).

Multi databases supported.

Encapsulated APIs for avoiding writing SQL statements.

Awesome fluent query API.

Alternative choice to use SQL still, but easier and better APIs than the originals.

More for you to explore.

用大白话来描述的话,可以列举如下:



Litepal使用了ORM(对象关系映射)模型

Litepal几乎是无配置的,仅需极少的配置文件

Litepal几乎包括所有的CRUD操作,也支持多张表格的操作

Litepal可以仅调用api进行CRUD操作而避免编写sql语句

总之,看到Litepal具有这么多良好的特性,读者是否心动了呢。理论的话不多说,我们现在就开始正式地使用Litepal进行数据库的相关操作

PS:如果有曾经学习过Java的ORM框架——Mybatis的读者,应该不会对Litepal的使用太陌生,因为它们都使用了xml文件进行相应的配置



3.演示

3.1 集成

现在Android框架的集成相比于IDE还为ADT的时代,要方便了许多。原因是现在的主流IDE是Android Studio,而AS默认使用了Gradle进行版本的配置管理,这让集成框架变得简单了许多。

在build.gradle下,添加以下语句,然后重新sync,即可将Litepal集成到你的项目中:



implementation 'org.litepal.android:java:3.0.0'

1

当然,目前Android的主流开发语言,除了Java之外,还有Kotlin,Litepal同样具有Kotlin版本的(这里的演示仅针对Java,Kotlin版本的异曲同工)依赖:



implementation 'org.litepal.android:kotlin:3.0.0'

1

可以根据个人需求进行配置。



3.2 配置

集成了Litepal之后,要想正式使用它还需要进行一些配置



在assets目录下新建litepal.xml,作为Litepal的全局配置文件,相应的条目信息已作出注释,代码如下:

<?xml version="1.0" encoding="utf-8"?>

<litepal>

    <!--  数据库名  -->

    <dbname value="androidframelearn"/>



    <!--  数据库版本号  -->

    <version value="1"/>



    <!--  指定映射模型  -->

    <list>

       

    </list>



    <!--  指定文件的存储方式  -->

    <!--  <storage value="external" />-->

</litepal>



在你的应用下配置Litepal,有两种方式可以实现:

修改清单文件,将你的应用名修改为:android:name="org.litepal.LitePalApplication"

新建一个自己写的MyOwnApplication类,然后将清单文件中的应用名定位到该类,即:android:name="com.example.MyOwnApplication",然后再编写MyOwnApplication类,代码如下:

public class MyOwnApplication extends Application {



@Override

public void onCreate() {

    super.onCreate();

    LitePal.initialize(this);

}

...

}



两种方式亦可,Litepal的作者建议若使用第二种方式,需要尽快地调用LitePal.initialize(this);所以将其放在onCreate()方法是最好的。



3.3 创建数据库

刚才在介绍的时候已经说过,Litepal采取的是对象关系映射(ORM)的模式,那么什么是对象关系映射呢?简单点说,我们使用的编程语言是面向对象语言,而使用的数据库则是关系型数据库,那么将面向对象的语言和面向关系的数据库之间建立一种映射关系,这就是对象关系映射了。

不过你可千万不要小看对象关系映射模式,它赋予了我们一个强大的功能,就是可以用面向对象的思维来操作数据库,而不用再和SQL语句打交道了,不信的话我们现在就来体验一下。像往常使用SQLiteOpenHelper类,为了创建一张Book表需要先分析表中应该包含哪些列,然后再编写出一条建表语句,最后在自定义的SQLiteOpenHelper中去执行这条建表语句。但是使用LitePal,你就可以用面向对象的思维来实现同样的功能了,定义一个Book类,代码如下所示:



package com.androidframelearn.dao_litapal;



import org.litepal.crud.LitePalSupport;



public class Book extends LitePalSupport {

    private int id;

    private String author;

    private double price;

    private int pages;

    private String name;

    public int getId(){

        return id;

    }

    public void setId(int id){

        this.id = id;

    }



    public String getAuthor(){

        return author;

    }

    public void setauthor(String author){

        this.author = author;

    }



    public double getPrice(){

        return price;

    }

    public void setPrice(double price){

        this.price = price;

    }



    public int getPages(){

        return pages;

    }

    public void setPages(int pages){

        this.pages = pages;

    }



    public String getName(){

        return name;

    }

    public void setName(String name){

        this.name = name;

    }

}



这里使用标签来声明我们要配置的映射模型类,注意一定要使用完整的类名。不管有多少模型类需要映射,都使用同样的方式配置在标签下即可。

没错,这样就已经把所有工作都完成了,现在只要进行任意一次数据库的操作,BookStore.db数据库应该就会自动创建出来。为了更好地演示代码,我们将布局文件所需要的功能一次性编写好,activity_main.xml代码如下:



<?xml version="1.0" encoding="utf-8"?>

<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:orientation="vertical">



    <Button

        android:id="@+id/btn_db_create"

        android:layout_width="match_parent"

        android:layout_height="wrap_content"

        android:text="创建数据库"/>



    <Button

        android:id="@+id/btn_db_query"

        android:layout_width="match_parent"

        android:layout_height="wrap_content"

        android:text="查询数据"/>



    <Button

        android:id="@+id/btn_db_insert"

        android:layout_width="match_parent"

        android:layout_height="wrap_content"

        android:text="插入数据"/>



    <Button

        android:id="@+id/btn_db_update"

        android:layout_width="match_parent"

        android:layout_height="wrap_content"

        android:text="更新数据"/>



    <Button

        android:id="@+id/btn_db_delete"

        android:layout_width="match_parent"

        android:layout_height="wrap_content"

        android:text="删除数据"/>



</LinearLayout>





接下来,修改MainActivity,除了给按钮注册点击事件,还需要编写不同的方法代表不同的逻辑,其中,创建数据库的方法代码如下:



private void createDBbyLitePal() {

        btn_db_create.setOnClickListener(new View.OnClickListener() {

            @Override

            public void onClick(View v) {

                Log.i(TAG,"创建数据库成功");

                LitePal.getDatabase();

            }

        });

    }



仅仅通过点击按钮,调用LitePal.getDatabase();这句api,就可以创建出数据库,让我们实际进入项目中尝试一下吧!点击该按钮,然后查看控制台,如图所示:



出现该句日记,说明数据库创建成功,接下来我们看看这个数据库是否按照我们所设置好的格式创建出来了,进入data/data/你的项目包名/databases,即可查看到该数据库已经放置到该目录下,如图所示:





3.4 升级数据库

事实上,若想对现有数据库进行升级,也是可以实现的。以前我们使用SQLiteOpenHelper来升级数据库的方式,虽说功能是实现了,但你有没有发现一个问题,,就是升级数据库的时候我们需要先把之前的表drop掉,然后再重新创建才行。这其实是一个非常严重的问题,因为这样会造成数据丢失,每当升级一次数据库,之前表中的数据就全没了。

而使用Litepal,就可以很好地避免这个问题。假设我们现在有一张新的表Category要加进去,同样编写它的实体类,代码如下:



package com.androidframelearn.dao_litapal;



public class Category {

    private int id;

    private String categoryName;

    private int categoryCode;

    public int getId(){

        return id;

    }

    public void setId(int id){

        this.id = id;

    }



    public String getCategoryName(){

        return categoryName;

    }

    public void setCategoryName(String categoryName){

        this.categoryName = categoryName;

    }



    public int getCategoryCode(){

        return categoryCode;

    }

    public void setCategoryCode(int categoryCode){

        this.categoryCode = categoryCode;

    }

}



改完了所有我们想改的东西,只需要记得在litepal.xml将版本号加1就行了。当然由于这里还添加了一个新的模型类,因此也需要将它添加到映射模型列表中。修改litepal.xml中的代码,如下所示:



<?xml version="1.0" encoding="utf-8"?>

<litepal>

    <!--  数据库名  -->

    <dbname value="androidframelearn"/>



    <!--  数据库版本号  -->

    <version value="2"/>



    <!--  指定映射模型  -->

    <list>

        <mapping class="com.androidframelearn.dao_litapal.Book"/>

        <mapping class="com.androidframelearn.dao_litapal.Category"/>

    </list>



    <!--  指定文件的存储方式  -->

    <!--  <storage value="external" />-->

</litepal>



重新运行一下程序,再次创建数据库,就可以完美地完成数据库的升级了。这里的调试可以使用sqlite工具,这里不再赘述。



3.5 插入数据

在讲述本节时,首先回顾一下之前添加数据的方法,我们需要创建出一个Contentvalues对象,然后将所有要添加的数据put到这个Contentvalues对象当中,最后再调用SQLiteDatabase的insert() 方法将数据添加到数据库表当中,步骤相当繁琐。

而使用LitePal来添加数据,这些操作可以简单到让你惊叹!我们只需要创建出模型类的实例,再将所有要存储的数据设置好,最后调用一下save()方法就可以了。

同样地,修改MainActivity,增加插入数据的事件方法,代码如下:



private void insertDatabyLitePal() {

        btn_db_insert.setOnClickListener(new View.OnClickListener() {

            @Override

            public void onClick(View v) {

                Book book = new Book();

                book.setName("The Da Vinci Code");

                book.setauthor("Dan Brown");

                book.setPages(454);

                book.setPrice(16.96);

                book.save();

                Log.i(TAG,"插入数据成功");

            }

        });

    }



同样运行程序,查看控制台,如图所示:



当点击查询数据(下一节将介绍该逻辑)时,控制台打印刚刚插入的数据,如图所示:





3.6 查询数据

使用Litepal同样可以很轻易地查询数据,当然了,由于篇幅限制,这里仅仅贴出最简单的查询方式,至于关联查询等稍复杂的查询方式,可以去GItHub上参考Litepal的官方文档进行相关调用即可。

同样地,修改MainActivity,增加查看数据的事件方法,代码如下:



private void queryDatabyLitePal() {

        btn_db_query.setOnClickListener(new View.OnClickListener() {

            @Override

            public void onClick(View v) {

                List<Book> books = LitePal.findAll(Book.class);

                for (Book book : books){

                    Log.i(TAG,"查询数据成功");

                    Log.d("MainActivity","书名是"+book.getName());

                    Log.d("MainActivity","书的作者是"+book.getAuthor());

                    Log.d("MainActivity","书的页数是"+book.getPages());

                    Log.d("MainActivity","书的价格是"+book.getPrice());

                }

            }

        });

    }



相关的运行结果上一小节以贴出,这里不再重复。



3.7 更新数据

更新数据要比添加数据稍微复杂一点,因为它的API接口比较多,这里我们只介绍最常用的几种更新方式。

首先,最简单的一种更新方式就是对已存储的对象重新设值,然后重新调用save()方法即可。那么这里我们就要了解一个概念,什么是已存储的对象?

对于LitePal来说,对象是否已存储就是根据调用model.isSaved()方法的结果来判断的, 返回true就表示已存储,返回false就表示未存储。那么接下来的问题就是,什么情况下会返回true,什么情况下会返回false呢?

实际上只有在两种情况下model.isSave()方法才会返回true, 一种情况是已经调用过model. save()方法去添加数据了,此时model会被认为是已存储的对象。另一种情况是model对象是通过LitePal提供的查询API查岀来的,由于是从数据库中查到的对象,因此也会被认为是已存储的对象。

由于查询API相对复杂,因此只能先通过第一种情况来进行验证。修改MainActivity中的代码,如下所示:



private void updateDatabyLitePal() {

        btn_db_update.setOnClickListener(new View.OnClickListener() {

            @Override

            public void onClick(View v) {

                Book book = new Book();

                book.setName("The Lost Symbol");

                book.setauthor("Dan Brown");

                book.setPages(510);

                book.setPrice(19.95); // 第一次设置商品价格

                book.save();

                book.setPrice(10.99); // 第二次设置商品价格

                book.save();

                Log.i(TAG,"更新数据成功");

            }

        });

    }



可以看到,我们做了跟插入数据类似的事情,但是我们对数据的价格进行了设置,运行程序,如图所示:



可以看到,除了刚刚插入的数据,还有第二条刚刚更新过后的数据。然而这种更新方式只能对已存储的对象进行操作,限制性比较大,接下来我们学习另外一种更加灵巧的更新方式,可以调用以下api:



book.updateAll("name = ? and author = ?","The Lost Symbol","Dan Brown");

1

这里仅贴出其中一条api,其他的可以参考官方文档,这里不再赘述。



3.8 删除数据

使用Litepal删除数据的方式主要有两种,第一种比较简单,就是直接调用已存储对象的delete()方法就可以了,对于已存储对象的概念,我们在之前已经学习过了。也就是说,调用过save()方法的对象,或者是通过LitePal提供的查询API查出来的对象,都是可以直接使用delete()方法来删除数据的。这种方式比较简单,我们就不进行代码演示了,下面直接来看另外一种删除数据的方式。

代码如下:



private void deleteDatabyLitePal() {

        btn_db_delete.setOnClickListener(new View.OnClickListener() {

            @Override

            public void onClick(View v) {

                LitePal.deleteAll(Book.class,"price < ?","15");

                Log.i(TAG,"删除成功");

            }

        });

    }



运行程序,删除过后,按照代码逻辑,已经删除掉了所有price小于15的条目,如图所示:





4.版本异同

之前阅读了郭霖老师所著《Android第一行代码 第二版》时,所记载的Litepal版本为:



compile 'org.litepal.android:core:1.4.1'

1

而的Litepal版本(Java版本,另有Kotlin版本,导入的依赖稍有不同)为:



implementation 'org.litepal.android:java:3.0.0'

1

新旧版本的主要区别是一些类名的划分,例如老板本的DataSupport变成了LitePalSupport,除此之外,一些api的名称也稍有变动,读者在使用时最好可以参考GitHub上的官方文档,及时更新代码,做到与时俱进。



5.源码地址

AFL——Android框架学习


TinyUI-TUIListView最简单的使用

seo达人

      在TinyUI简介的博客中提到其特点中包含一条,即多数大控件的使用方法和android一直,除了语言差异之外,本篇我们就介绍列表控件TUIListView最简单的使用方法。



        列表组件/控件作为目前Android/iOS的APP中最常用的控件,该控件的设计同时参考Android、windows、Qt等使用的经验进行筛选,最终选择了Android的ListView设计,其他平台的列表中使用难以程度或设计上略逊于Android,因为Android给与了开发者最大的发挥控件,你可以在列表中可以显示任何控件。



        TUIListView中的每一行每一列你可以放置任何UI组件,使用TUIListView需要配合TUIAdapter进行使用,而TinyUI和Android一样提供了内置的简单使用的TUISimpleAdapter。TUISimpleAdapter主要用于显示文本(即每一行都是只能显示文字),如果需要在列表中显示其他UI组件,则需要自定义一个Adapter,关于自定义Adapter将在后续进行详细讲解。



        本篇既然是TUIListView最简单的使用,我们就使用TUISimpleAdapter来显示文本你列表,TUISimpleAdapter最好只用于数据步发生变化的情况,因为其存放的数据使用了C++标准库的vector容器,而非使用list容器,vector容器的特点是访问速度快,但其缺点是vector的内存是连续的,因此内容发生变化可能会造成内存申请和拷贝的动作;而list容器使用的双向链表,其特点是插入数据快,但访问速度慢。



        本篇我们仍然使用上一篇中自定义的MyWindow来显示TUIListView。



使用方法/步骤

  1. 定义listView和andapter



            MyWindow中包含TUISimpleAdapter.h的头文件,并定义listView和adapter



    MyWindow.h


    ifndef MY_WINDOW_H

    define MY_WINDOW_H

    include <TUIWindow.h>

    include <TUISimpleAdapter.h>

     

     

     

    class MyWindow : public TUIWindow

    {

    public:

        MyWindow(TUIWindow* parent = nullptr);

        virtual ~MyWindow();

     

        void onShow();

        void onClose();

     

    private:

        TUIListView listView;

        TUISimpleAdapter adapter;

    };

     

    endif // !MY_WINDOW_H

     


  2. 填充数据,并把adapter设置到listView中



    MyWindow.cpp


    include "MyWindow.h"

     

     

     

    MyWindow::MyWindow(TUIWindow* parent)

        : TUIWindow(parent)

    {

        setContentView(&this->listView); // 把listView作为当前窗口的内容视图

     

     

        vector<string> data; // 使用vector<string>类型的data存放数据

     

        for (int32_t i = 0; i < 20; i++)

        {

            data.push_back(to_string(i)); // 生成0~20的数值-转换成字符串,放到data中

        }

     

        this->adapter.setData(data); // 把data设置到adapter中

     

        this->listView.setAdapter(&this->adapter); // 把adapter设置到listView,作为listView数据来源和操作对象

    }

     

    MyWindow::~MyWindow()

    {

    }

     

    void MyWindow::onShow()

    {

    }

     

    void MyWindow::onClose()

    {

    }

    到目前为止窗口显示列表控件已全部完成,接下来和上一篇一样调用MyWindow的show()方法即可显示,最终结果如下图所示:


call、apply、bind 原理实现

seo达人

目录

  1. call 的模拟实现
  2. apply 的模拟实现
  3. bind 的模拟实现
  4. 三者异同



    学习并参考于:



    JavaScript深入之call和apply的模拟实现



    JS的call,apply与bind详解,及其模拟实现





    (一)call的模拟实现

    call 用法 : MDN Function.prototype.call()



    call() 方法使用一个指定的 this 值和可选的参数列表来调用一个函数。



    call() 提供新的 this 值给当前调用的函数/方法。


  5. call 实现主要思路:

    将函数设为对象的属性



    执行该函数



    删除该函数



    另外还有考虑:



    call 函数还能给定参数执行函数

    this 参数不传,或者传null,undefined, this指向window对象

    函数是可以有返回值的
  6. 实现:

    Function.prototype.myCall = function () {

      if (typeof this !== 'function') {

        throw new TypeError('error!')

      }

      let context = arguments[0] || window   //this 参数可以传 null,当为 null 的时候,视为指向 window

      context.fn = this  // 首先要获取调用call的函数,用this可以获取

      let args = [...arguments].slice(1) //从 Arguments 对象中取值,取出第二个到最后一个参数   

      let result = context.fn(...args)  //函数是可以有返回值的

      delete context.fn

      return result

    }


  7. 测试:

    // 测试一下上面实现的myCall

    var value = 2;



    var obj = {

        value: 1

    }



    function bar(name, age) {

        console.log(this.value);

        return {

            value: this.value,

            name: name,

            age: age

        }

    }



    bar.call(null); // 2



    console.log(bar.myCall(obj, 'kevin', 18));

    // 1

    // Object {

    //    value: 1,

    //    name: 'kevin',

    //    age: 18

    // }



    (二)apply 的模拟实现

    apply 用法:MDN Function.prototype.apply()



    apply() 方法使用一个指定的 this 值和可选的参数数组 来调用一个函数。



    apply 的实现跟 call 类似。


  8. 实现:

    Function.prototype.myApply = function () {

      if (typeof this !== 'function') {

        throw new TypeError('error!')

      }

      let context = arguments[0] || window

      context.fn = this

      let result = arguments[1] ? context.fn(...arguments[1]) : context.fn()

      delete context.fn

      return result

    }


  9. 测试:

    var foo = {

        value: 1

    }

    function bar(name, age) {

        console.log(name)

        console.log(age)

        console.log(this.value);

    }

    bar.myApply(foo, ['black', '18']) // black 18 1



    (三)bind 的模拟实现

    bind 用法:MDN Function.prototype.bind()



    bind()方法会创建一个新函数,称为绑定函数。当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this,之后的一序列参数将会在传递的实参前传入作为它的参数。



    bind是ES5新增的一个方法,不会执行对应的函数,而是返回对绑定函数的引用。


  10. 实现:

    Function.prototype.customBind = function () {

      if (typeof this !== 'function') {

        throw new TypeError('error!')

      }

      const that = this   // 首先要获取调用bind的函数,用this获取并存放在that中

      let context = arguments[0] || window

      const args = [...arguments].slice(1)

      return function() {

        return that.apply(context, args.concat([...arguments]))

      }

    }



    (四)三者异同
  11. 相同:

    改变函数体内 this 的指向
  12. 不同:

    call、apply的区别:call方法接受的是参数列表,而apply方法接受的是一个参数数组。

    bind不立即执行。而call或apply会自动执行对应的函数。


JavaScript 的简述与基础语法

前端达人

目录

JavaScript

  1. JS 发展历史
  2. JS 的特点
  3. JS 的组成
  4. JS 的基础语法

    a. 两种引入方式 type src

    b. 三种输出方式 console.log document.write alert

    c. 变量声明 var let const typeof undefined

    d. 数据类型简介 string number boolean object undefined

    e. 运算符 + - * / % = < > && || !

    i. 全等符与不全等符 === !==

    f. 流程控制语句

    i. 条件语句 if else switch case default break

    ii. 循环语句 while dowhile fori forin forof



    JavaScript

    • JS 用于完成页面与用户的交互功能;

    1. JS 发展历史
    JavaScript 在 1995 年由 Netscape 公司的 Brendan Eich,在网景导航者浏览器上首次设计实现而成。因为 Netscape 与 Sun 合作,Netscape 管理层希望它外观看起来像 Java,因此取名为 JavaScript。但实际上它的语法风格与 Self 及 Scheme 较为接近;
    欧洲计算机制造联盟(ECMA)在 1997 制定脚本语言规范 ECMA Script1 (ES1),2009 年发布了 ECMA Script5(ES5),在 2015 年发布了 ECMA Script 2015(ES6),所有的浏览器的都支持 ES6;

  5. JS 的特点

    JS 设计模仿了 Java 语言,不同如下:

    JS 不需要编译,由浏览器直接解释执行;

    JS 是弱类型语言,JS 变量声明不需要指明类型,不同类型的数据可以赋值给同一变量;
  6. JS 的组成

    ECMA Script 是 JS 的基础语法;

    BOM(Brower Object Model)是浏览器对象模型;

    DOM(Document Object Model)是文档对象模型;
  7. JS 的基础语法

    a. 两种引入方式 type src




    <!DOCTYPE html>
    <html lang="zh">
    <head>
    <meta charset="UTF-8">
    <title>JS两种引入方式</title>
    </head>
    <body>
    <!-- JS两种引入方式:JS和CSS一样都需要引入到html页面中,浏览器才会解释执行,JS有两种引入方式:
        1.内嵌(内部)脚本:在script标签中写js代码,script标签推荐放置在body标签的底部,理论上和style标签一样可以放置位置任意;
        2.外部脚步:使用script标签的src属性引入外部js文件,使用注意事项: script标签使用了src属性以后内部的代码不再被浏览器解释执行,script引入外部脚本时不能使用自闭合格式 -->
    <!--告诉浏览器把解析器切换为js解析器 type="text/javascript"可以省略-->
    <script type="text/javascript"> document.write("<h1>内部脚本</h1>");//向body中追加html内容 </script>
    <script src="../js/外部脚本.js"></script>
    </body>
    </html>
    






    b. 三种输出方式 console.log document.write alert

    <!DOCTYPE html>
    <html lang="zh">
    <head>
    <meta charset="UTF-8">
    <title>JS三种输出方式</title>
    </head>
    <body>
    <!-- JS三种输出方式:
        1.输出到浏览器控制台;
        2.输出html内容到页面;
        3.浏览器弹框输出字符 -->
    <script>
    //1.输出到浏览器控制台
    console.log("1. 输出到浏览器控制台");//开发者专用
    //2.输出html内容到页面
    document.write("2. 输出html内容到页面");//向body中追加html内容
    //3.浏览器弹框输出字符
    alert("3. 浏览器弹框输出字符");//阻塞执行
    </script>
    </body>
    </html>
    


    c. 变量声明 var let const typeof undefined


    <!DOCTYPE html>
    <html lang="zh">
    <head>
    <meta charset="UTF-8">
    <title>JS变量</title>
    </head>
    <body>
    <!-- JS变量用来存放数据;
        es5以前变量声明使用var;
        es6之后变量声明使用let,常量声明使用const。他们用于替代es6的var声明方式;
     JS是弱类型语言: 
        声明变量时不知道变量的类型(undefined),只有在赋值之后js变量才确定类型;
        typeof(a) 或 typeof a 输出变量的类型;
        undefined表示变量未赋值,未知类型 -->
    <script>
    //字符串 Java声明 String str ="张三";
    let str ="张三";
    console.log(str);
    //整数 Java声明 int k = 5;
    let k = 5;
    console.log(k);
    //小数 Java声明 float f = 7.5;
    let f = 7.5;
    console.log(f);
    //常量 Java声明 final Integer PI = 3.14;
    const PI = 3.14;
    console.log(PI);
    //演示弱类型语言
    let a;//声明变量不需要指明类型
    console.log(typeof a);//undefined 未赋值类型,未知类型
    a = "你好";
    console.log(typeof a);//string
    a = 123;
    console.log(typeof a);//number
    a = true;
    console.log(typeof a);//boolean
    a = new Object();
    console.log(typeof a);//object
    </script>
    </body>
    </html>
    


    d. 数据类型简介 string number boolean object undefined


    <!DOCTYPE html>
    <html lang="zh">
    <head>
    <meta charset="UTF-8">
    <title>JS数据类型</title>
    </head>
    <body>
    <!-- JS数据类型,常用数据类型:
        1.string 字符串类型;
        2.number 数字.包括整数和小数类型;
        3.boolean 布尔类型.值只有true和false两个;
        4 object 对象类型,空对象使用null表示,有两种格式:
            new Object(); 
            JSON格式.例如:{name:"张三",age:18};
        5.undefined 变量未赋值 -->
    <script>
    //1. string 字符串
    let str = "你好";
    console.log(str);
    console.log(typeof str);//string
    // 2. number 数字
    let n = 123.456;
    console.log(n);
    console.log(typeof n);//number
    // 3. boolean 布尔类型
    let boo = false;
    console.log(boo);
    console.log(typeof boo);//boolean
    // 4. object 对象类型,空对象使用 null表示
    let obj = null;//或 new Object();
    console.log(obj);
    console.log(typeof obj);//object
    // 5. undefined 变量未赋值
    let u = undefined;
    console.log(u);//值是undefined
    console.log(typeof u);//类型是undefined
    // Object类型
    let stu = new Object();//创建一个js对象,js对象的属性想要直接加上
    stu.id = 1;
    stu.name = "刘一";
    stu.age = 18;
    console.log(stu);//{id: 1, name: "刘一", age: 18}
    console.log(typeof stu);//object
    // JS对象取属性值有两种方式:
    // 1. obj.key
    console.log(stu.name);//刘一
    // 2. obj["key"]
    console.log(stu["name"]); //刘一 == stu.name
    let b = "age";
    console.log(stu[b]);//可以取不定属性的值
    </script>
    </body>
    </html>
    


    e. 运算符 + - * / % = < > && || !


    i. 全等符与不全等符 === !==


    <!DOCTYPE html>
    <html lang="zh">
    <head>
    <meta charset="UTF-8">
       <title>JS运算符</title>
    </head>
    <body>
    <!--
    JS运算符
    js运算符和Java运算符基本相同
    只有一个特殊的比较运算符
    === 判断js变量的值和类型都相等才为true
    !== 不全等,判断js变量的值和类型有一个不等就为true
    -->
    <script> let a = 3;
    let b = "3";
    console.log(a == b);//true
    // 全等 运算符 ===
    console.log(a === b);//false
    // 不全等 运算符 !==
    console.log(a !== b);//true
    // 三元(三目)运算符 布尔表达式?真:假
    let str = a===b?"全等":"不全等";
    console.log(str);//不全等
    </script>
    </body>
    </html>
    


    f. 流程控制语句

    i. 条件语句 if else switch case default break


    <!DOCTYPE html>
    <html lang="zh">
    <head>
    <meta charset="UTF-8">
    <title>条件语句</title>
    </head>
    <body>
    <!-- 条件语句JS的条件语句和Java语法基本一样,但是对数据类型的真假判断有些区别 JS中对各种数据类型作为布尔值的特点:(重点掌握) 1. string 空字符串""为false,其余都为true 2. number 数字 只有0为false,其余数字都为true 3. boolean 布尔类型 值只有 true和false 两个
    循环语句
  8. object 对象类型 空对象null表示false,其它对象都是true 5. undefined 变量未赋值 为false 常用语法格式 if ... else if ... else switch case break default -->
    <script>
    //if ... else
    //if(true){
    //if(""){// string 只有空字符为假
    //if(0){number 只有0为假
    //if(false){//boolean false为假 true为真
    //if(null){//object null为假
    //if(undefined){//undefined永为假
    if("undefined"){//undefined永为假
    console.log("满足条件");
    }else{
    console.log("不满足条件");
    }

    //switch case break default
    let k =1;
    switch (k) {
    case 1:
    console.log("111");break;
    case 2:
    console.log("222");break;
    default: console.log("其它情况"); }
    </script>
    </body>
    </html>


    ii. 循环语句 while dowhile fori forin forof


    <!DOCTYPE html>
    <html lang="zh">
    <head>
    <meta charset="UTF-8">
    <title>循环语句</title>
    </head>
    <body>
    <!-- 循环语句
        while,do while,fori 和Java一样;
        forin
            1.遍历出数组中的索引和元素
            2.遍历出对象中的属性和元素
        forof 
            1.遍历出数组中的元素
        forin 与 forof 区别:
            1.forin可以遍历对象,forof不能遍历对象
            2.forin可以遍历出数组中的索引,forof只能遍历出数组中的元素 -->
    <script>
    //while 和Java一样
    let k=1;
    while (k<3){
        console.log(k++);
    }
    
    //do while 和Java一样
    k =1;
    do{
        console.log(k++);
    }while (k<3)
    
    //fori 和Java一样
    for(let i=0;i<3;i++){
        console.log(i);
    }
    
    //forin 可以遍历数组和对象
    let arr = ["刘一","陈二","张三"];//JS数组使用中括号[]定义
    let stu = {id:5,name:"李四",age:18};//JS对象使用大括号定义
        //1.forin 遍历出数组中的索引
    for(let index in arr){
        console.log(index);//数组的索引 0,1,2
        console.log(arr[index]);//数组中的元素
    }
        //2.forin 遍历出对象中的属性名key
    for(let k in stu){
        console.log(k);//字符串属性 id,name,age
        console.log(stu[k]);//对象中的属性值
    }
    
    //forof 可以遍历数组
    for(let e of arr){
        console.log(e);//数组中的元素
    }</script>
    </body>
    </html>
    
    
    
    
    
    ————————————————
    版权声明:本文为CSDN博主「Regino」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/Regino/article/details/105321573
    

原文链接:https://blog.csdn.net/Regino/article/details/105321573 





产品简化改版怎么做?

资深UI设计者

编者按:这篇文章来自 Taras Bakusevych 的文章《How to simplify your design》

如今的数字产品,在不断迭代过程中,会加入更多功能,添加更多的技术支持,更新进阶的特性。而另一方面,企业对于构建简单实用的产品,也同样非常着迷。这当中当中毫无疑问是存在微妙的对抗和冲突的——如何让更好更多的信息,以更加轻量易用的方式呈现出来?这就涉及到产品的简化改版的技巧了。

到底怎么算是简单?

让产品变简单,其实一件困难的事情。

我们可以将「简单」定义为「更加易于理解或完成」,或者是「不困难」。值得一提的是,简单与否,它是主观的,对于一个人而言简单,对于另一个人而言不一定简单。通常,我们是通过下面的三个步骤来判断一件事情是简单还是困难:

在《简单法则》当中,John Maeda 提供了10条法则,这些法则能够帮你平衡商业、技术、设计所带来的复杂性——更通俗的来说,就是如何合理简化但是又能收获更多。

The Laws of Simplicity, John Maeda

Meada,作为麻省理工多媒体实验室的教授,他也是举世闻名的平面设计师。在书中,他非常慎重地探讨了「改进」这一概念,在他看来,它通常并不总意味着「更多」。无论是对于复杂度,还是简化,都只是相对概念。结合合理的培训,培养制造火箭的科学家也不是难事,但是总会有一些关键的因素,会让简单的事情变复杂,在设计中我们应该尽量去规避它们:

那么,我们在具体的产品改版中,要如何合理地简化呢?下面,是我结合这些原则来总结的21条建议:

1、创造有焦点价值的产品

有太多的产品试图让你更用户做更多的事情,每个企业都试图成为行业中的瑞士军刀。但是,如果你希望产更加简单,那么你需要有一个核心价值,并且确定产品真正针对的是谁,并非每个产品都应该成为 Facebook。你的产品的核心价值是什么?

2、删除所有不必要的内容

想要简化产品,最简单的方法,是经过深思熟虑之后再进行简化和删除。如果组件或者模块的功能、价值存疑,那么请删除。次要信息、不常用的控件、分散注意力的样式,都在这个范畴内。就这么简单。一旦采用这一的方式,你会立刻马上看到结果。当然,删除的时候,请务必深思熟虑。

「简单并非完全没有混乱,因为杂乱必然会伴随简化而存在。简单本质上是对产品的定位和目的有更精准的描述。而没有杂乱仅仅意味着这是一件不杂乱的产品,但是这并非简单。」

——乔纳森·艾维

3、将数据转换为有意义的样式

我们日常设计的绝大多数产品,都涉及到需要用户理解大量数据。当用户对于趋势和变化感兴趣的时候,请尽量使用可视化的方式来呈现信息,而不是一堆数字。尝试从数据中提取有效的内容,可视化地呈现在用户眼前,这才是有意义的简化。

4、提供对快速决策的支持

用户长期遭受复杂决策机制的折磨,帮助用户看清各种信息,更好的决策是简化的方向之一。席克定律曾经对此作出过非常经典的解释:用户做选择所需要花费的时间和精力往往会随着选项数量的增加而增加。因此,如果你希望用户决策变得简单,那么你需要简化选择,消除不必要的选项,来帮助用户进行选择。

5、太多选择会吓走用户

当前心理学理论和调研肯定了一件事,就是足够多的选择可能会给人带来积极的情绪,相应的衍生出一个流行的观点,就是选择越多越好,但是人类天性中的管理能力是不足以支撑过多选择的。

果酱实验是消费心理学当中最著名的实验之一:为消费者提供更少的选择,对于销售更加有利。这一点是至关重要的——更少的选择,能够带来更多的销量。

在这个实验当中,选择较少所带来的转化率几乎是更多选择的转化率的10倍。这是一个选择过载带来不良后果的一个重要范例——过多选择抑制了用户选择购买的想法。

6、在提供多种选择的时候,给用户以建议

如果多个选择总是无法避免,那么不妨给用户提供建议,或者告诉用户多数用户的选择。向用户清楚地传达选项之间的差别,这在定价策略上会带来更多积极的效果。

7、将用户注意力吸引到对的区域

当你了解用户流程的时候,就应该有针对性将用户注意力引导到的对的目标上去,找到关键区域,让用户在界面中优先注意到它们。

8、使用色彩和版式来呈现内容的结构层次

你应该经常会听到类似「可读性很差」这样的表述。有很多设计的确存在这样的问题。在设计当中,有太多的因素会影响信息的传达——字体的选取,大小变化、间距、大小写和配色,都会影响到层次结构,影响到用户汲取信息的方式。使用正确的配色和版式,让内容的层次结构更加清晰,最好还能反映出品牌特征,这样就更能增强吸引力和用户对它的印象了。

9、优化组织结构,便于管理

下面两张图当中,要你数清楚有多少个圆点,哪一张图会让你这个过程更快?

如同你所看到的,无序的点状方阵和有序的是截然不同的,后者的认知负担更低,识别速度更快。面对无序的信息和内容,我们需要逐个计数,但是面对有序的信息,则截然不同。

有组织的元素不仅有着更高的识别度,也更容易被记住。在数字产品当中,记住常见控件的位置和功能无疑是重要的。同样是上面这张图,如果不是计数,而是记住每个点的位置,你能不能做到?毫无疑问,缺少组织结构,这是一件艰难的事情。

10、给相关内容分组

在简化复杂页面结构的时候,对组件和内容进行分组通常是最为有效的方法之一。通过层级划分,用户每次需要处理的信息量都会更小,而不是在无关的组件中来回穿梭寻找。借助边框和卡片将相关度更高的内容整合到一起,这是非常便捷的方法。此外,关于关于分组,格式塔原理中的分组原则是非常值得借鉴的:接近性、相似性、连续性、闭合性和连通性。

11、拆分任务,按列布局

几乎任何产品当中都会涉及到不同类别的表单,这是获取用户信息的一种方式。有的时候,即使删除掉一些不必要的字段,表单依然会很长。因此,我们可以将巨大的表单或者任务拆分开来,这样一来,整个执行过程会变得简单不少。

设计表单的时候,尽量使用单列而非多列,这样更加顺畅、易于填写。用户无需思考接下来要填写什么,而是按照一条线继续操作下去即可。

12、透明清晰地展现路径和状态

不确定性让人感到焦虑,尽可能地在设计当中规避这种不确定性。这就是为什么要让用户在任何时候都清晰地知道他在流程中所处的位置,以及接下来会发生什么。总结之前所提供的信息也是个好主意,能够减轻用户的负担,避免反复检查之前的内容。

13、替用户进行计算

人脑对于涉及计算的部分通常会比较费力,对于这些不太占用计算力的内容可以交由系统来进行计算,和计算相比,人的大脑在处理具象化的信息的时候更加擅长。尝试利用系统,而不是将压力转嫁给用户。

14、用逐步展现来隐藏复杂性

渐进式的展开是用户体验设计当中的一种范式,这种方法能够让界面更容易被理解。当涉及到多屏、大量信息和操作的时候,这种逐步展现的机制,可以避免让用户感到不知所措,也可以隐藏一些无关的信息,直到最后用户可以清晰明白内在的关联性为止。逐渐展现的方法,遵循着「从抽象到具体」这样的一种概念,而 iOS 系统当中,所采用的导航方式,无疑就是这种思路的典范。一屏一个步骤,逐渐展现,最后给用户一个确切的结果。

15、善用通用的交互模式和设计范式

用户将绝大多数的精力都耗费在各种各样的其他数字产品上,这意味着用户对于其他的网站和APP的交互模式、使用方法都有着清晰的了解,对于特定的模式有具体的预期。因此,无论你是设计网站,某个 APP ,甚至是冰箱上的控制系统,都可以遵循既有的设计和交互模式。这并不是意味着停止创新,而是在很多事情上,你无需重新造轮子。

16、初次体验应当精简

在设计任何产品的时候,都应该让用户尽快感知到产品的价值。因此,除非是满足功能性的需求,在用户初次打开应用的时候,都尽量把其他的干扰去掉,因为这都是让用户了解产品价值的障碍。第一印象很重要,如果不满意,很多用户会立即离开。

如果你第一次尝试,即使是最简单的操作,可能也是有挑战性的,有时候新手使用 APP 的时候需要引导,但是最好的方法是尽量让体验和功能一目了然,搭配上下文环境的说明引导,而不是提供复杂繁琐的学习材料。

17、结合使用场景,巧用人体工程学

简约易用的产品大多能够合理地结合人体工程学的设计。绝大多数人可能会想到诸如汽车座椅、控制面板和手柄这样的案例,但实际其实远远不止。人体工程学适用于几乎所有涉及人的产品设计。

1954 年,心理学家 Paul Fitts 仔细了解了人的运动系统,并且提出了费茨定律——移动到目标所需要的时间取决于和目标之间的距离,并且和目标大小成反比。这个定律反馈到具体的界面设计上,就是尽可能让常用元素更大,并放置到更容易被触及的地方。

18、提供直接编辑功能和推荐参数

删除不必要的交互组件、视图或者是步骤,是简化过程中必不可少的部分呢。用户应该可以以最快的速度进行操作,比如在表单中直接操作,而不是在弹出框中进行编辑,这个被称为「流程状态」,「流程状态」不应当被弹出框打破。此外,对于很多需要填写的部分,最好提供参数推荐建议功能,就像搜索的实时推荐,让用户可以更为准确地输入。

19、使用智能默认值,减少认知负荷

智能默认值——或者说智能占位符是一个非常有用的策略。一方面可以帮助用户更快更精准的填写表单,另一方面,也给用户提供了相对准确的范例。只不过,默认参数的确定,需要设计师和开发团队对用户的使用场景等信息进行研究,通过测试和调研,确定用户使用状况,以此来确定默认参数应该是多少。如果需要明确地参数或者选项,那么可以将默认值设置为 90% 用户可能会选的选项,并辅以说明。

20、防止出错

出错信息会给用户带来很大的压力,为了避免用户陷入这样的状况,防止用户无法完成任务,请使用数据输入检查的功能,对于格式输入错误的表单和内容,对用户予以警报和提醒,避免错误发生。对于极为重要的操作,在用户提交之前,让用户二次确认信息,做好检查。对于某些强制性、破坏性乃至于不可恢复的操作,确保用户知道这一操作带来的影响。

21、无障碍设计

数字产品的可访问性是老话题了,要让产品和设计对于所有人——包括有视觉障碍的用户,都可以轻松地使用。最常见的,就是不要使用色彩来作为传达信息的唯一途径,确保文本和背景之间有足够的对比度,支持键盘导航操作等等。可访问性问题并不限于特定的群体,坚持不断的改善和提升,有助于每一个用户的体验。

结语

简化并创造易于理解的产品并不容易,但是这是一条几乎任何产品都要走的必经之路,简化的方法和技巧有很多,虽然零碎,但是它们最终会带着产品走向一条更好的道路。

文章来源:优设    作者:Taras Bakusevych

这份上万字的指南,帮你学会用栅格系统构建响应式设计

资深UI设计者

今天,90% 的媒体互动都是基于屏幕的,通过手机,平板,笔记本电脑,电视和智能手表来与外界产生联系。多屏设计已成为商业设计中不可或缺的一部分,响应式设计正迅速成为常态。作为 UI 设计师,我们希望为我们的产品在不同尺寸下都能为用户提供良好的用户体验,栅格系统可以帮助我们做到这一点。

即使是我们只针对一个尺寸进行设计,我们也经常面临设计布局方面的问题。合理运用栅格系统可以帮助我们控制布局结构并实现一致和有组织的设计。栅格系统就像无形的胶水一样凝聚一个设计,即使元素看上去是彼此分离,但通过网格将它们连接在一起,实现良好的层次结构,位置关系和一致性。

设计师和开发者之间的协作过程中,栅格系统在前端开发中是被应用的很广泛一套体系,许多优秀的设计都使用了栅格系统,使用栅格系统可以加速开发并保证视觉还原。栅格系统虽然是传统设计方法中的一部分,但它仍旧能帮助我们去设计这个多终端的世界。看到这里,你可能非常想知道栅格系统在页面中是如何运作的,那么今天我们一起来学习并且实践我们的格栅系统。

「The grid system is an aid, not a guarantee. It permits a number of possible uses and each designer can look for a solution appropriate to his personal style. But one must learn how to use the grid; it is an art that requires practice.」

「栅格系统可以帮助我们设计,但却不能保证我们的设计。它有多种可能的用途,并且每个设计师都可以寻找适合其个人风格的解决方案。但是必须学习如何使用网格。这是一门需要实践的艺术。」

——Josef Müller-Brockmann《平面设计中的网格系统》作者

什么是栅格系统?

栅格系统可以让你依靠秩序与逻辑去完成设计。

早在 20 世纪初,德国、荷兰、瑞士等国的平面设计师们发现通过维持视觉秩序,从而使版面能更加清晰有效地传递信息,二战后这种理念在瑞士得到了良好的发展,直到 20 世纪 40 年代后期,第一次出现了使用网格进行辅助设计的印刷作品。由瑞士设计师大师 Josef Müller-Brockmann(约瑟夫·米勒-布罗克曼)所著的《平面设计中的网格系统》一书,自 1961 年出版以来畅销至今,对设计界有着深远的影响。史称 Swiss Typography Movement (瑞士新浪潮平面设计运动),后来成为全球风靡的 International Typographic Style (国际主义设计风格))。

△ 约瑟夫·米勒一布罗克曼 (Josef Muller-brockmann, 1914-1996)

瑞士的一位平面设计师和教师。1958 年任《新平面设计》(New Graphic Design)主编 1966 年被任命为 IBM 的欧洲设计顾问。布罗克曼因他的极简主义设计与简洁的排版、图形和色彩而闻名,他的设计对 21 世纪的众多平面设计师都产生了重大影响。

栅格系统的优势

1. 减少决策成本提高设计理解力

栅格系统在页面排版布局、尺寸设定方面给了设计者直观的参考,它让页面设计变得有规律,从而减少了设计决策成本;UI 设计也是需要理性的、客观的、具有数学逻辑美感的。熟练运用网格系统能够让你的设计更有秩序和节奏感,页面信息的展现更加清晰,提高阅读效率,从而提供给用户舒适的使用体验。加快认知速度。这意味着用户在使用产品完成特定的任务时,例如发送消息,预订酒店房间或乘车。用户能够连贯地理解并找到下一条信息或下一步要采取的步骤。

2. 响应化

因为人们使用不同类型的设备与产品进行互动,从智能手表的小屏幕到超宽屏电视,交互是流畅的,并且没有固定的尺寸。使用产品时,人们通常会在多个设备之间切换,以完成该产品的单个任务。所以响应式设计不应该是一种品,而是一种必需品。这意味着设计师不能再为单个设备的屏幕构建。多设备环境迫使设计人员根据动态网格系统进行思考,而不是固定宽度。使用网格可以跨不同屏幕尺寸的多个设备创建连贯的体验。

3. 加速团队协作设计

当多位设计师共同设计产品时,一个统一标准就变得尤为重要。如果没有一个统一的框架去约束的话,我们的产品的页面和组件的标准可能各式各样,这样的话整个产品的页面都会比较混乱。因此,网格系统有助于将界面设计工作分开,因为多位设计师可以在统一的布局下进行不同部分工作,并且无缝集成并保持连贯。

4. 加速开发并保证视觉还原

大多数设计项目的实施,涉及到设计者和开发者之间的协作。栅格化提高了页面布局的一致性和复用性;避免了设计师与开发者在细节上的反复沟通确认,从而提升了整个设计开发流程的效率、并能帮助开发者实现较为理想的设计还原。

栅格系统的基本构成

1. 列和槽(Columns and Gutters)

列(Columns) 和槽(Gutters)。列(Column)是内容的容器,水槽(Gutter)用来调节相邻两个列的间距,把控页面留白;列和列间距加上页面边距(Margin)加起来屏幕的水平宽度。列和列间距的内容区域(Content width)由 N个列和(N-1)个水槽组成。通常情况下,web 端采用 12 列,平板采用 8 列,手机采用 4 列。当然,你可以根据项目特点来设计你的网格系统,列和水槽的宽度我们可以利用 8 点网格系统来定义,下面会讲到。列的数量越多,页面就会被分割得越「碎」,在页面设计时就会越难把控,适用于业务信息量大、信息分组较多、单个盒子内信息体积较小的页面设计,列间距宽度数值对页面的影响,与外边距大体类似,即间距越大页面越轻松简单,反之亦然。用户已经习惯通过鼠标滚轮或滚动条(scrollbar)来纵向浏览页面内容,因此竖直方向可以无限延伸,所以栅格系统在竖直方向的栅格可以不体现出来,我们在执行设计时只要在水平方向保持规律的变化就可以了。

2. 页面边距(Side Margins)

页面边距就是内容区域(Content field)以外的空间,比较推荐的设计就是页面边距可以随着屏幕尺寸的增大而增大。页面边距在移动设备上通常是 12Px到 40Px 之间,在平板设备和桌面设备页面边距变化就相当多了。在响应式设计中,你选择了一个页面边距之后,缩小页面宽度时页面还是会有你设置的最小页面边距,直到到达下一个响应点(breakpoint)。当你增大页面宽度时,页面就有更多的页面边距,直到页面宽度到达下一个响应点(breakpoint)。

3. 模块(Field Elements)

模块就是你的设计区块,可以是一段文字,一张图片,或是其他更加丰富的元素。背景元素并不能算作是设计模块,所以并不需要遵循栅格系统。模块的定义是很灵活的,它可以是个小的单位或是元素,也可以是一个元素丰富的区块。

以 12 栅格系统为例,一个 12 栅格系统可以根据业务需要被 2 等分、3 等分、4 等分、6 等分、12 等分,还可以被 1:1:1、1:2:1、1:3:2、2:3:3、1:2、1:3、1:5、3:5 等不对称分割,具体采用哪种比例的组合需要我们根据自己业务需求来定。

4. 8 点网格(8pt spatial system)

栅格系统大的层面可以帮助设计者更好的进行版式设计与内容布局,而小的方面可以辅助设计师规范页面内各种元素的对齐与间距的设定。从用户体验角度来讲,这两者同等重要,从执行层面来讲,我们一般先做版式设计与布局,然后再填充内容、调整细节。

由于列跟水槽的宽度是以网格作为基本单位来增加或者减小,所以栅格化的重要一步就是需要先定义好栅格的原子单位「网格」的大小。目前最普适易用的就是 8 点网格。我们也可以利用 8 点网格法来制定产品中的间距,建立 8 点为一个单位的网格,使用 8 的倍数来定义模块的间距与元素的尺寸。8 点网格有如下几点优势:

  • 目前主流桌面设备的屏幕分辨率在竖直与水平方向基本都可以被 8 整除,使用 8 作为最小原子足够普适。可以确保不同布局之间的视觉一致性,同时可以灵活的适配多种尺寸的设计。以 8 为单位符合「偶数原则」。偶数原则可以在页面缩放中的避免类似于 0.5、0.75、1.25 等次像素的出现,从而使页面各类元素在大多数场景下都能有比较精致的细节表现。
  • 在网格系统中应该更加注重的是间距,而间距要遵循网格系统(例如使用 4、8、16、24、32 等和 8 具有规律的数字)同时在产品中的各类元素也要遵循这类原则(例如图标大小、组件大小等)。所以布局的水平和垂直节奏和各个组件的节奏会相互重叠,整体的设计将更加完整。
  • 开发工程师使用的前端开源组件库比如 Metronic、Antdesign 等也是基于 8 的原子单位来设计,因此如果设计师也使用以 8 为基本单位的栅格系统,开发与设计师相互对接就会更加方便,开发实现页面时也能更高品质地去还原我们的设计。

如果设计上没有立即可识别的间距系统时,这种设计可能会让用户感觉廉价、不一致,而且通常不值得信任。如果设计上遵循一个 8pt 网格系统时,节奏变得可预测和视觉上的愉悦。对于用户来说,这种体验是经过修饰和可预测的,这增加了用户对品牌的信任和喜爱。

无论有多少个设计师在协同合作,现在都有一个一致的间距规范,决策成本将大大降低。设计师可以轻松地从另一个设计师停止的地方开始设计,或者轻松地并行构建。我们定义下规范可以及时和开发同学沟通,因此可以为工程师节省时间。

5. 基线网格(Baseline Grid)

基线网格由密集的水平行组成,这些行提供文本的对齐和间距准则,类似于您在直纹纸上书写的方式。在下面的示例中,每 8px 行在红色和白色之间交替。

△ 基线网格

提示:将所有行高设置为基本单位(8x 或 4px)的增量非常重要,这样您的文本才能与基线网格完美对齐。

△ 字体行高

响应式设计

1. 什么是响应式?

设计师需要通过设计让内容在不同的平台上体验最大化,确保让用户在任何一个屏幕上看到内容的时候,会觉得这些内容就是为这个平台而设计的,而不是单纯的缩放而来。这种无缝的体验,才是跨屏幕设计的真正难点所在。想要制定一套针对不同设备和屏幕的设计方案,你需要一整套的策略。用户体验同时包含了性能、交互、效率等多方面内容,也就是说,对于一个线上的响应式页面,我们不仅要关注视觉上看到的,也要关注我们操作、使用时的感受,这些综合因素最终影响着用户使用时的效率与体验。

2. 响应式设计的核心步骤

确保核心的用户体验

虽然用户体验是无处不在的,但是对于特定产品,最核心的体验是存在的。产品通常是用来解决用户所面临的特定问题的,它的这一特质让产品变得有意义。关键的内容和关键的功能的组合,通常构成了产品的核心用户体验。如果你并没有想明白这个问题,不妨问问自己:用户需要完成哪些最常见/最重要的任务?找到问题的答案之后,你的产品就应当从各个方面、各个渠道,完整而全面地支撑这些功能,帮助用户完成这些任务。举个例子,Uber 的核心用户体验是随时随地叫车,无论设备的屏幕大小如何,你进行的设计全部都应该围绕着这个需求和功能来进行。叫车是 Uber 的核心功能,即使使用 Apple Watch 这种极小的屏幕尺寸都应该顺利地完成这个任务。

敲定你的产品所覆盖的设备类型

现在的移动端设备屏幕尺寸各不相同,单独为某一个设备设计内容无疑是不现实的。根据你的产品覆盖人群、受众分类、使用场景,综合考虑你的内容会优先呈现在哪些设备和平台上,然后有意识地筛选出常见的设备类型:手机,平板,桌面端,智能电视,智能手表……

不同的设备组合通常是基于不同的场景、需求和服务来构成的,用户会针对不同的屏幕进行不同模式的交互,甚至处理的内容也会有差异。比如说,在手机上,用户更加倾向于使用轻量级的任务,并且进行一定量的沟通和交流。在平板上,用户行为更多集中在内容消费上,并且目前平板的使用量被认为在逐步降低。桌面端依然是用户完成较为专业、复杂任务的首选平台,足以应付复杂多样的内容。了解各种设备类型和使用场景是用来构建用户体验的关键。

针对不同内容来匹配用户体验

并非所有的内容都符合不同设备的使用场景,比如智能手表就不适合展示大量的文本内容。你的产品所覆盖的设备组当中,每种设备的使用场景不同,应该匹配的用户体验也不一样。移动端用户和桌面端用户的需求就是不同的,场景差异也很大。以 Evernote 为例,它可以在多种不同类型的设备之间同步和切换,其桌面端版本就针对用户的内容需求进行了优化:Evernote 的桌面端应用程序针对阅读性的内容和多媒体进行了优化,而移动端的 Evernote 则强化了拍摄记录、图片和音频记录的功能:其次,不同的设备屏幕具备不同的输入方式,设计师如果忽略输入方式上的独特性,也常常会出现许多问题,这里就不扩展开来了。

优先为最小的屏幕做设计

一直以来,设计师都习惯从最大的屏幕着手设计,最后考虑最小的屏幕上的显示效果,这意味着绝大多数的设计都是从桌面端开始设计的,通常桌面端的内容和功能更全面。当桌面端的整体设计完成之后,再推进到其他设备端的设计。然而,在进行桌面端设计的时候,我们常常会遭遇「厨房水槽」困境:由于产品通常会牵涉到多个利益相关方,许多多余的功能会被加入进来。而实践经验表明,移动端优先的设计往往能够更好的专注于核心功能,更适合作为产品设计的起点。当你优先设计最小屏幕所需要的界面的时候,这种局面会强制你从最关键最重要的地方开始设计。这也是之前设计圈和产品开发领域一直所强调的「移动端优先」的策略的由来。在此之后,再进行平板、桌面和电视端的设计,就是一个自然地做加法的过程了。在绝大多数的案例当中,最小屏幕通常是手机屏幕。

测试你的设计

产品的测试环境并不一定都得是在现实世界中寻找,但是在尽可能让真实的用户来做可用性测试,并且在产品发布之前解决所有的用户体验上的问题。

3. 为何要利用栅格系统来进行响应式设计?

响应式可以响应的前提有两点:1、页面布局具有规律性、2、元素宽高可用百分比代替固定数值,而这两点正是栅格系统本身就具有的典型特点,所以利用栅格系统进行响应式的设计是顺理成章的,也比较快捷,所以响应式与栅格化天生一对好搭档。

如何建立栅格系统

第1步:确定列的数量

第一阶段先不要限制自己的列数。首先,创建一个低保真或高保真的原型。设计一些基本元素和用户流程。在此之后,就开始设计最优的列数和大小。如果在项目开始设计之后不得不改变我们的栅格系统,不要有负担,我们需要有一些试错的空间。

我们在设计页面时,用到最多的布局方式就是等分布局,即页面内容区域被 N 等分,每一份的宽度则根据屏幕宽度自适应调整。那么就从这个角度出发,思考一下页面的网格应该设置为多少列,才能的满足各种等分布局的需要。与 web 类似,移动端最方便的网格之一是 12 列网格。这个网格将允许我们在一行中同时放置偶数和奇数个元素。

对于移动端来说,12 列网格的缺点是一个列的宽度太小,你可能很少创建一个列宽度的元素。如果你选择 2、4 或 8 列网格,请记住在一行中放置奇数个元素可能会出现的问题。

Pro-Tip:

界面设计通常包含数百个不同的页面,因此,一个网格可能不适合所有的页面。如果需要,创建额外的栅格系统,但不要忘记设计的一致性。网格系统的一致性:相同的布局边距、列之间相等或成比例的水槽,以及更改列本身的宽度时其他模块也需要保持相同的比例。

第2步:定义水槽和边距

首先,让我们先翻阅目标屏幕的设计 Guideline,以找出通常页面边距(Side Margins)。目前,Android 和 iOs 的最小推荐布局边距为 16pt。web 端则依照屏幕尺寸不同而不同。这意味着,如果你希望遵循系统指南,则页面边距不应小于 16pt。(但可以更大的)

在选择 12 列网格时,列之间的水槽不应该太大,因为由于列的宽度小和它们之间的大宽度的水槽,列将在视觉上产生分裂的感觉。同时我建议你选择与8pt 间距系统成比例的水槽大小。所以布局的水平和垂直节奏会相互重叠。水槽与页面边距成比例。那么网格更加一致,也将允许我们轻松地在其中放置特殊元素,如轮播(carousel)。

第3步:定义 8pt间距系统

了帮助不同设计能力的设计者们在界面布局上的一致性和韵律感,统一设计到开发的布局语言,减少还原损耗。在大量的实践中,我们提取了一组可以用于 UI 布局空间决策的数组,他们都保持了 8 倍数的原则、具备动态的韵律感。经过验证,可以在一定程度上帮助我们更快更好地实现布局空间上的设计决策。定义网格系统方法很多,如运用 8 点网格系统、斐波那契数列、某最小原子单位的增量、从底层系统参数化定义间距等,我们以最小原子单位的增量为例去定义网格系统。最小单元格的数值选择需要从两方面考虑:

  • 一方面是该数值是否能被大多数手机屏幕的宽度整除,即广泛的适用性;
  • 另一方面是在具体使用时是否具有一定的灵活性。

在适用性方面,4、6、8、10 这四个数值都是基本可以满足的,在灵活性方面,4px 表现最佳,但是页面就会被分割的非常细碎,在设计时比较难于把控。因此我们需要根据 APP 的实际情况选择合适的数值,4px 或 6px 单元格比较适合页面内容信息较多,布局排版比较复杂的产品。而 8px 单元格对一般的设计场景都可以很好的满足,比较适合大多数的 项目,因此是比较推荐使用的。

那么假设我们以 8 为基准的去延展系统间距,得到如下间距系统:

1、2、8、16、24、32、40、48、56、64、72、80、88、96、192 等,这里都是 8 的倍数或能被 8 整除

但是目前间距数量太多,过于细碎也会导致间距比较乱,所以我们继续优化梳理(以 6 为基准,前面个数是后面个数的 2 倍递增),得到以下间距系统:

1、2、8、16、24、32、48、64、80、96

第4步:sketch布局设置

利用 sketch 的布局设置功能,即可快速搭建出网格系统的参考布局,在平时做设计的过程中,可以经常使用 Ctrl+L 快捷键切换布局的显示,提高设计效率。

我们来解释一下这些设置分别是什么:

  • Total Width:就是内容区域(Container)的值;
  • Offset:表示栅格的偏移量,我们只要设定完成以后按 Center 按钮即可,会自动居中;
  • Number of Columns:就是栅格数;
  • Gutter on outside:是非常重要的设置,勾选以后才能跟前端的栅格算法匹配;
  • Gutter Width:就是栅格之间的间距;
  • Columns Width:就是栅格的宽度。

如何做到响应式?

在传统的栅格化系统设计中,列的宽度和水槽的宽度是保持不变的,只是列的「数量」发生变化。为什么要这么处理呢?这是为了让设计更简单。如果一组三张卡片分别放在桌面的四列上,那么在平板电脑上,会显示两张卡片,并把第三张卡片进行折行显示在第二行上。不需要做任何的调整,因为已经知道它位于第四列上了。在手机上,答案也很简单,只需要一张卡片,其他的就会自动堆到下面的行中。但是目前我有更多的响应策略,例如当视窗(Viewport)发生变化时,内容区域的元素如何去响应,具体到我们当前的栅格系统,就是 Columns、Gutters、Margins 以及由 Columns 跟 Gutter 组成的盒子(BOX)四者的值(主要是宽度)如何变化,以及在这种变化之下我们页面的布局如何调整。

1. 固定栅格或是断点系统(Fixed boxes or breakpoint system)

固定网格,列宽和水槽宽不会改变,只是改变列的数目,当窗口缩放时,排版布局不会发生任何改变,只有当达到一个临界值(开发那边设置好的固定的值),界面才会发生改变。在此之前界面排版都是不变的,就像一部分被切掉了。

如果开发那边写了一个固定栅格,当你从桌面缩小到平板电脑,就像是在桌面的浏览器宽度时,你不会看到任何变化,设计就像是被剪掉了一样。但当达到平板屏幕尺寸临界点时,设计布局马上就会改变,平板电脑上的显示效果就会好起来。如果继续减小这个值,同样的事情也会发生,在到达另一个临界值之前,设计看起来都是不变的。下面是常见的断点系统(Breakpoint System)

如图,响应式是以视窗的最小宽度作为基本依据来制定每种宽度下 Columns、Gutters、与 Margins 的响应策略,也就是说 Viewport Min-width 是做出响应的触发条件,视窗每达到一个最小宽度,就会触发该宽度下预设的页面布局方式,而每种布局都是在该宽度下的最佳布局,也是因此,响应式才会在各种复杂分辨率条件下都能给用户比较好的体验。
每个视窗宽度的最小值是触发响应的关键值,因此我们给这些用于触发的关键值起了个名字叫「Breakpoint」,每个 Breakpoint 触发一种响应策略。

2. 流动栅格(Fluid Grid)

流动栅格系统是编辑内容,仪表板,图像,视频,数据可视化等理想的响应策略。当窗口缩小时,内容将动态地发生变化,文本会进行换行,元素也会变窄。然而,这些元素在内容宽度缩小到下一个临界值之前,布局是不会变化的。在各种情况下,对用户来说,扩展内容的大小比扩展可见内容的数量更有用。

所以我想说的是,断点 BreakPoint 只是一个更改布局的参考点。这就是为什么列宽和水槽的数量不会改变的原因,因为我们想让设计师在考虑布局时能够更容易地创建一致性。内容宽度会随着窗口的缩放而发生改变,例如图片会缩小,文本会换行。水槽的宽度不一定是固定的,可以随着页面宽度变化。

在每个断点处,列计数是固定的,列宽度是最小网格 8PT 的倍数。行高是列大小的倍数,遵循推荐的纵横比。边距和填充是小单位的固定倍数。在断点之间,实际列宽是网格区域的百分比,而不是一个小的单位倍数。内容尺度流畅。

首先从所以屏幕大小中选择一个基本尺寸,然后按照推荐的纵横比以基本大小的倍数构建每个响应式尺寸。当每个块使用相同基础大小的倍数时,就会出现网格。遵循此方法可确保栅格系统一致性,甚至跨产品的一致性。

3. 混合栅格(Hybrid Boxes)

在实际项目中,使用流动网格和固定网格的组合也是常见的做法。网站通常是流动网格,因为它要去适应各种不同终端的大小。后台系统设计、工具型的界面设计就比较经常使用网格和流动网格组合的形式。例如的后台管理系统(dashboard)侧边栏是固定网格,右侧内容是流动网格。混合栅格在每个维度上有不同的缩放规则,所以它们不使用统一的缩放比。当用户需要调整浏览器的大小以使内容在一个维度上伸缩而在另一个维度上不伸缩时,便使用混合网格。

面板对栅格系统的影响

1. 灵活面板(Flexible panels)

灵活的面板允许折叠和扩展状态。面板的展开状态为固定宽度,用户无法调节。当用户将鼠标悬停在折叠的面板上时,面板就会展开。当灵活的面板扩展时,它们要么压缩内容和网格,要么将内容推到浏览器边缘之外。

2. 固定面板(Fixed panels)

固定面板保持静态宽度,不能折叠,也存在于响应网格之外。

3. 悬浮面板(Floating panels)

此面板样式漂浮在主要内容区域之上,不影响响应网格。浮动面板将任何 UI 元素隐藏在其下方,用户必须将其移除。内联菜单、下拉菜单和工具提示也是浮动的。

总结

写这篇文章的目的是想提供一些关于如何在响应式设计中使用栅格系统,我知道对于我自己来说,我花了很多时间理解网格是如何工作的。我在 YouYube 上看了很多视频,也阅读了大量的文章,但每个人都在关注它为什么重要,却不去注重到底怎么在自己的项目中使用这些原则。

你要做的最好的事情就是从现在开始注意那些优秀设计是如何对齐元素的,你将会开始阅读这些设计系统。为了帮助理解,这里有一些设计系统概述了它们的网格使用:

在完全理解了网格的工作原理之后,你将成为了一名更好的设计师,因为你知道了你的设计将如何在临界值之间进行转换。你也可以落地你的设计,使它们能够达到像素级完美。这样的规范带来了更一致,更简洁的设计,当用户从一个界面到另一个界面流转时,这真的提升了产品的档次。我建议在你的设计中去应用这些网格,并和开发同学一起,以实践的方式将它们落地,这将会是一个非常不错的进步。

文章来源:优设    作者:IvanZheng

免费可商用!这款中文楷体太适合做封面字了!

资深UI设计者

有时候做国风的设计作品,如果选择黑体和宋体可能太过端正,但选择书法字体的话,可能因为飞白或过度连笔导致字体不容易识别,那选什么字体呢?我建议大家选择楷体,注意不是选系统自带的楷体,而是今天推荐的演示悠然小楷,字体手稿由一位美丽女生书写,其字体有着悠然自得、闲情逸致的气质。

「演示悠然小楷」是由 keynote 研究所 x 秋叶 PPT 联合发布的一款免费商用字体,推荐设计师们收藏或下载。下载地址见文末。

字体案例演示

1. 青春/知识

△ By@伟崇

2. 文学/历史

△ By@伟崇

3. 游戏/小说

△ By@小敏

4. 中式地产

△ By@伟崇

5. 商务风

△ By@伟崇

8. 主视觉设计

△ By@画生

△ By@画生

9. 海报物料

△ By@画生

△ By@画生

△ By@画生

10. 电商广告

△ By@画生

△ By@画生

△ By@画生

11.文字排版

△ By@画生

△ By@画生

12. 品牌形象设计延展

△ By@画生

△ By@画生

△ By@画生

字体搭配

1. 思源宋体

△ By@澄音

2. 方正宋刻本秀楷

清刻本悦宋用得多了,不如试试这款有些相似但又有不同的「方正宋刻本秀楷」,较之前者字形更为周正,笔画更为果决,娟秀之中蕴藏力量。

△ By@画生

3. Garamond

Garamond 是一款古朴传统的衬线字体,在西文体系中历史悠久,恰因没有特殊的个性而被广泛使用,是老式衬线体中最具代表性的字体,与同样以端正工整为名的楷书作搭,最能相互映衬。

△ By@画生

4. 阿里巴巴普惠体

基础黑体的字体没有复杂的修饰,进一步弱化次要信息,强烈对比下让主角得以更好的突显,也让画面层次感更加丰富,而基础型的黑体,必然首推阿里巴巴普惠体。

△ By@画生

字体下载

  • 字体名称:演示悠然小楷字体
  • 系统名称:slideyouran(有的软件显示不出中文字体,就用这个来搜索)
  • 网页 CSS 字体属性:font-family:slideyouran
  • 字体版权:永久免费,包括商用

下载地址:https://pan.baidu.com/s/1ohOK2RSEA9vsfHAbfvpZmw 提取码:ypae

如何让深色模式更精致?

资深UI设计者

通过一些案例进行比较与实验,探索如何将 UI 深色模式设计得更好。

iOS 作为 UI/UE 设计的风向标,一直是行业的引领者,不管你愿不愿意承不承认,他的每一次更新变化总能带动 UI 设计行业的一些大大小小的变革,并且产生更多的追随者,比如当年的 iOS7 开始的扁平化设计风格,对后续设计风格的影响直到现在已经 7 年了。

在最近半年,iOS 在 UI 设计风格上最大的变革莫过于 DarkMode(深色模式),在 DarkMode 之前,我们熟悉的 UI 界面往往都是浅白色界面为主,而从 iOS13 开始正式使用了 DarkMode,界面突然可以变深色了,苹果官方说这样设计可以让眼睛更舒服地长时间阅读,为革命保护视力,而且更加省电增长续航,具体结果我们不得而知,需要科学家们去验证了,但是对于我们设计师来说带来的挑战就是要「黑白无常」了。

其实 DarkMode 从测试版算起已经差不多推出了有半年的时间了,一些知名的 APP 产品早已经有了自己的兼容方案,同时 iOS 原生界面也给了我们很多最佳实践案例,按道理大家对于 DarkMode 的设计方式方法应该已经掌握了差不多了,但直到这几天微信正式推出了自己的 DarkMode 兼容方案,才发现对 DarkMode 的探索还需要设计师们多多努力。谨以此文表达一下自己的观点,不妥之处敬请海涵。

从一个「列表页面」说起

列表视图(TableView)是 iOS 中最常见的界面组件,一般常见于设置与栏目列表页面:

iOS 设置界面的浅色模式和深色模式看起来都非常协调。

下面我们看看微信发现页面,这个页面和 iOS 设置是很相似的。

如果单独看微信发现页面的浅色模式实际效果还是不错的。

但是直接转换到深色模式下就感觉突然差的很多了,甚至可以说是有点难看,这次微信真的做了一次黑天鹅?

到底是什么原因让微信发现页面在深色模式下视觉体验如此之差呢?

我们不妨将两个功能布局都相似的深色进行放在一起进行一下比较:

组成色彩分析:

在色彩这块在这两个页面中微信和 iOS 基本保持一致,四层灰度设计能更好的保持页面整体干净整洁且层次分明,但是在 A 背景色上,微信的背景色选择了黑色偏绿的颜色,应该是微信设计师还是想体现出产品的标志色原色。

文字的颜色也较 iOS 略微深一点,但是在整体上影响并不大。

看来在主要色彩上并没有什么问题,那么为什么微信这个界面与 iOS 界面比起来视觉上要感觉差一些呢?

下面来看一下图标

图标设计分析:

图标上的差别主要在于线宽与外框,微信采用无外框统一线宽的线形图标,iOS 采用的是有外框剪影图标。

我们我们把图标进行互换会怎么样呢?

观察到了吗?别看错了!

是的,我故意把位置做了对调,左边是 iOS,右边是微信。替换图标后的微信明显加分不少,整个界面都整齐多了,而 iOS 换了图标后明显变得不够整齐了,潦草很多。

那么结论是微信的无框线性图标在深色模式下兼容有问题?是的,的确如此。但是等一下,还有一些细节你注意到吗?换了图标的微信界面和之前的 iOS 界面比起来明显还是有点不够整齐,为什么呢 ?

我们回过头来从细节再看一下 iOS 界面。

我们按照这个思路把刚才微信替换图标界面再排序一下!

△ 界面视觉体验明显整齐了很多是不是!

疑问:

为什么细线图标和无框图标会在深色背景表现不够好,而在浅色背景下就没问题呢?

是不是所有的 UI 都会存在这样的问题呢?

我们再来看一些例子:

看来结论是一样的,线性图标在深色背景下的表现都是差强人意,反观带框图标适应性很强,浅色和深色模式下均能良好的适配,我来分析一下原因。

当年伽利略用望远镜往天上看,发现木星比金星大,换成肉眼看后金星则比木星大。他认为是眼睛的某种视觉特性造成了这种现象。

德国物理学家赫尔曼把这种错觉称为辐照错觉,就是说在黑暗背景下,亮度越高的物体看起来面积越大。

再来看一张图片

哪个圆圈看起来更大,显然是黑色背景下的白色圆形,实际上这只是一种错觉,所有圆圈是一样大。

光亮刺激会使得神经元产生非线性放大作用,导致刺激比实物本身看起来更大,白色圆形更亮,所以看起来更大一些。

线性图标是用线条勾画图案达到隐喻效果,一般线粗是 2px~6px 像素。

设计师在设计时都是以最终视觉作为参考,而设计稿本身多是浅色背景,所以在浅色背景的映衬下图标视觉会显得稍大,视觉基本是平衡的,假如设计是 4px 而呈现出的效果其实是 6px 左右。

是不是觉得哪里有点不对了?按照这个逻辑黑色背景下白色线图标不应该是视觉更大、更明显吗?

我们还需要考虑一个因素,那就是色彩,之前的几个界面案例的线性图标都是彩色的,特别是黑色背景下,不同色彩的图标放在一起,会有明显的忽大忽小的感觉,会让界面感觉非常凌乱。

是不是感觉黄色最大,红色的最小?但是其实是一样的,这还是相同形状的,要是图标形状不同感受会更明显。

看一个实际中的例子:

由于都是单色线性图标,在浅色和深色下表现还都不错的,但是单色图标略显界面单调,并不太建议这么设计。

毫无疑问,未来的 UI 场景需要适配多背景色风格,图标除了具备好看隐喻之外,更需要具备抗干扰性。

带框图标是一个不错的解决方法,大胆预测带框图标会将成为未来一段时间图标设计主流!

结论

  • 深色模式中灰度色阶在一个界面最多可分为四层。
  • 为了适配深色模式,今后有框图标将会成为图标设计风格主流。
  • 同样为了适配深色模式,细线图标将会被淘汰,剪影和粗线图标会流行起来。

  • 图标除了个体设计上用心,在排列上也会极大影响到页面的整合视觉,光谱排列法是个不错的选择。

  • 文章来源:优设    作者:残酷de乐章

日历

链接

个人资料

蓝蓝设计的小编 http://www.lanlanwork.com

存档