前端学习之路


  • 首页

  • 归档

  • 分类

  • 标签

  • 搜索
close
小朱

小朱

前端学习之路

168 日志
37 分类
37 标签
RSS
GitHub
友情链接
  • 极客学院
  • caniuse
  • codepen
  • JS Bin
  • Babel在线编译
  • Iconfinder图标
  • 在线JSON格式化
  • 智能图像压缩

JavaScript-基础概念

发表于 2017-04-03   |   分类于 JavaScript

柯里化

柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。柯里化声称“如果你固定某些参数,你将得到接受余下参数的一个函数”。如对于有两个变量的函数 yx,如果固定了 y = 2,则得到有一个变量的函数 2x。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 普通的add函数
function add(x, y) {
return x + y;
}
// Currying后
function curryingAdd(x) {
return function (y) {
return x + y;
};
}
add(1, 2); // 3
curryingAdd(1)(2); // 3

不可变数据

不可变数据(Immutable Data)的思想就是,一旦创建就不能被修改的数据,所有对 Immutable Data 的改变,最终都会返回一份新复制的数据,各自的数据并不会互相影响。

函数式编程

函数式编程是一种编程范式,和面向对象的编程方式一样,是一种编程思想。它属于”结构化编程”的一种,主要思想是把运算过程尽量写成一系列嵌套的函数调用。它的原理将电脑运算视为函数的计算,它的基础是 λ 演算,λ 演算的函数可以接受函数当作输入和输出。最主要的特征是,函数是第一等公民。和指令式编程相比,函数式编程强调函数的计算比指令的执行重要。和过程化编程相比,函数式编程里函数的计算可随时调用。函数式编程认为,只有纯的没有副作用的函数,才是合格的函数。

1
2
3
4
5
6
7
8
9
(1 + 2) * 3 - 4;
// 传统的过程式编程,可能这样写:
var a = 1 + 2;
var b = a * 3;
var c = b - 4;
// 函数式编程,可能这样写:
var result = subtract(multiply(add(1,2), 3), 4);

React-Native-上传下载

发表于 2017-04-03   |   分类于 React Native

文件的上传下载 react-native-fetch-blob

  • NPM install

    1
    npm install --save react-native-fetch-blob
  • Automatically Link Native Modules

    1
    react-native link react-native-fetch-blob
  • AndroidManifest.xml:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    ...
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    ...
    <intent-filter>
    <action android:name="android.intent.action.MAIN" />
    <category android:name="android.intent.category.LAUNCHER" />
    <action android:name="android.intent.action.DOWNLOAD_COMPLETE"/>
    </intent-filter>
  • Notice
    对于下载路径 RNFetchBlob.fs.dirs 的结论如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    CacheDir:"/data/user/0/com.gps/cache" ---- ios需越狱;android需root权限才能看
    DocumentDir:"/data/user/0/com.gps/files" ---- ios需越狱;android需root权限才能看
    MainBundleDir:"/data/user/0/com.gps" ---- ios系统app才能用;android需root权限才能看
    DCIMDir:"/storage/emulated/0/DCIM" ---- 不支持ios;android的DCIM目录
    DownloadDir:"/storage/emulated/0/Download" ---- 不支持ios;android的Download目录
    MovieDir:"/storage/emulated/0/Movies" ---- 不支持ios;android的Download目录
    MusicDir:"/storage/emulated/0/Music" ---- 不支持ios;android的Download目录
    PictureDir:"/storage/emulated/0/Pictures" ---- 不支持ios;android的Download目录
    SDCardDir:"/storage/emulated/0" ---- 不支持ios;android的Download目录

    个人感觉下载时应该下载到DocumentDir或CacheDir中,这两个个目录是用户不可见的,只用当用户显示发出了保存到相册的动作时才应该把下载的图片放到相册,通过调用React Native 提供的CameraRoll可以达到这个效果,这样用户对相册中图片的任何操作才不会对系统产生影响。

  • Usage
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    import React, { Component } from 'react';
    import { View, Text, StyleSheet, ScrollView, Image, Platform, CameraRoll, TouchableHighlight, Dimensions } from 'react-native';
    import { Button } from 'antd-mobile';
    import RNFetchBlob from 'react-native-fetch-blob';
    import Icon from 'react-native-vector-icons/FontAwesome';
    import Global from '../styles/Global';
    import NavBar from './NavBar';
    import { default as config } from '../config/base';
    import { getCommonHeaders } from '../utils/fetchUtil';
    const dirs = RNFetchBlob.fs.dirs;
    const WINDOW_WIDTH = Dimensions.get('window').width;
    const WINDOW_HEIGHT = Dimensions.get('window').height;
    class TestFileStream extends Component {
    constructor(props) {
    super(props);
    this.state = {
    downImageView: null,
    fileList: [],
    };
    this.uploadFile = this.uploadFile.bind(this);
    this.downloadFile = this.downloadFile.bind(this);
    this.getFilesInfo = this.getFilesInfo.bind(this);
    this.goBack = this.goBack.bind(this);
    }
    goBack() {
    this.props.navigator.pop();
    }
    uploadFile() {
    const data = '{\"SERV_ID\":\"'+'OPMS_EVENTATTACH_RECORD_PAGE'+'\",'
    + '\"FILE_CAT\":\"'+'ATTACH_IMG'+'\",'
    + '\"DATA_ID\":\"'+'0S8iwECYm1d68jb4FElvoKDb'+'\",'
    + '\"FILE_SORT\":' + 0 +'}';
    const commonHeader = getCommonHeaders();
    const header = {
    'ACCESS_TOKEN': commonHeader.ACCESS_TOKEN,
    'Content-Type': 'multipart/form-data',
    };
    RNFetchBlob.fetch('POST', `${config.applicationIpAndPort}/file?data=${encodeURIComponent(data)}`,
    header,
    [{
    name : 'Filedata',
    filename : 'cdpImage.png',
    data: RNFetchBlob.wrap(Platform.OS === 'android' ? 'file://'+ dirs.DCIMDir + '/cdpImage.png' : dirs.DocumentDir + '/cdpImage.png')
    }]
    ).then((res) => {
    console.log('上传成功:', res.text());
    alert("上传成功,浏览器中有信息");
    }).catch((err) => {
    console.log('上传失败:', err);
    alert("上传失败,浏览器中有信息");
    });
    }
    downloadFile() {
    RNFetchBlob.config({
    fileCache : true,
    path : Platform.OS === 'android' ? dirs.DCIMDir + '/logo.png' : dirs.DocumentDir + '/logo.png',
    }).fetch('GET', `https://facebook.github.io/react/img/logo_og.png`, {
    //some headers ..
    }).then((res) => {
    // 下载成功: /storage/emulated/0/Download/logo.png
    console.log('下载成功:', res.path());
    const imageStyle = {
    borderWidth: 1,
    width: 100,
    height: 100
    };
    const downImageView = (
    <View style={{justifyContent: 'center', alignItems: 'center'}}>
    <Image source={{ uri : Platform.OS === 'android' ? 'file://' + res.path() : '' + res.path() }} style={imageStyle}/>
    </View>
    );
    this.setState({
    downImageView,
    });
    // 如果是 ios,同时下载到相册
    if (Platform.OS === 'ios') {
    CameraRoll.saveToCameraRoll(res.path(), 'photo');
    }
    }).catch((err) => {
    console.log("下载失败:", err);
    alert("下载失败,浏览器中有信息");
    });
    }
    getFilesInfo() {
    // 显示文件,返回数组
    RNFetchBlob.fs.ls(Platform.OS === 'android' ? dirs.DCIMDir : dirs.DocumentDir)
    .then((files) => {
    console.log("dirs.DocumentDir目录中的文件:", files);
    this.setState({
    fileList: files,
    });
    }).catch((err) => {
    console.log("获取失败:", err);
    });
    }
    openFile(fileName) {
    alert(`打开文件:${fileName}`);
    }
    showFileList() {
    return this.state.fileList.map((item, index) => {
    return (
    <TouchableHighlight
    style={styles.container}
    underlayColor="rgba(34, 26, 38, 0.1)"
    onPress={() => this.openFile(item)}
    key={index}
    >
    <View style={{flexDirection:'row'}}>
    <View style={styles.icon}>
    <Text style={styles.iconText}>{index}</Text>
    </View>
    <View style={styles.textContent}>
    <Text style={[styles.title, ]}>{item}</Text>
    </View>
    </View>
    </TouchableHighlight>
    );
    });
    }
    render() {
    return (
    <View style={[Global.flex1, {width: WINDOW_WIDTH}]}>
    <NavBar
    title={this.props.route.params.title}
    goBack={this.goBack}
    />
    <ScrollView style={styles.temporaryTask}>
    <View style={[Global.containerCenter, styles.marginBottom20]}>
    <Text style={{fontSize: 15}}>测试上传、下载的服务为:</Text>
    <Text style={{fontSize: 15}}>OPMS_EVENTATTACH_RECORD_PAGE</Text>
    </View>
    <View style={Global.flex1}>
    {this.state.downImageView}
    <Button
    type="primary"
    onClick={this.downloadFile}
    style={styles.marginBottom20}
    >
    <Icon name="cloud-download" size={20} /> 下载文件
    </Button>
    <Button
    type="primary"
    onClick={this.uploadFile}
    style={styles.marginBottom20}
    >
    <Icon name="plus" size={20} /> 上传文件
    </Button>
    <Button
    type="primary"
    onClick={this.getFilesInfo}
    style={styles.marginBottom20}
    >
    <Icon name="folder-open" size={20} /> 获取文件列表
    </Button>
    {this.showFileList()}
    </View>
    </ScrollView>
    </View>
    );
    }
    }
    const styles = StyleSheet.create({
    temporaryTask: {
    marginTop: 20,
    marginBottom: 10,
    marginHorizontal: 20,
    // alignItems: 'stretch',
    flex: 1,
    width: WINDOW_WIDTH - 40,
    },
    marginBottom20: {
    marginBottom: 20,
    },
    container: {
    width: WINDOW_WIDTH - 40,
    flexDirection:'row',
    backgroundColor: '#BFEFFF',
    borderRadius: 7,
    alignItems: 'flex-start',
    marginBottom: 10,
    },
    content: {
    flex: 1,
    },
    icon: {
    width: 35,
    height: 35,
    margin: 10,
    backgroundColor: '#108ee9',
    justifyContent: 'center',
    alignItems: 'center',
    flexDirection: 'row',
    },
    iconText: {
    fontSize: 28,
    color: '#fff',
    fontWeight: '400',
    },
    textContent: {
    flex: 1,
    padding: 10
    },
    title: {
    color: '#101010',
    fontSize: 17,
    fontWeight: "400",
    },
    });
    export default TestFileStream;

React-Native-依赖

发表于 2017-04-02   |   分类于 React Native

使用过的react-native依赖

react-native-vector-icons 图标

react-native-image-picker 相机、相册

react-native-circular-progress 原型进度条

react-native-slider 水平滑动条

react-native-smart-amap 高德地图

react-native-smart-amap-location 高德定位

react-native-file-opener 打开文件

react-native-fetch-blob 文件上传下载

react-native-device-info 获取设备信息
react-native-imei 获取设备IMEI码
react-native-orientation 监听设备方向变化、设置屏幕方向锁定
react-native-sqlite-storage SQLite数据库
react-native-config 配置环境变量
react-native-push-notification 消息提醒

React-Native-IOS使用高德地图

发表于 2017-03-24   |   分类于 React Native

高德地图key值

  • http://lbs.amap.com/,注册帐号
  • 登录后,进入控制台,应用管理->我的应用,添加新key
    • Bundle ID值为 Xcode 切换到 General 标签,查看 Bundle Identifier的值

地图及定位

在github上找了好几个使用React Native封装基于ios的高德地图的依赖,但是大多数依赖无论手动连接还是自动连接都会有报错,个人感觉最大的可能就是不兼容现在的react-native^0.42.0,最后找到两个可以在IOS上显示的高德地图依赖和一个可以定位的依赖。

react-native-maps

可以显示高德地图,配置过程也非常简单,只需执行如下两个命令,运行时模拟器中地图显示的是英文,真机上地图显示的汉字,但是样式看上去有点奇怪,不知道是不是版本老的原因。

  • npm install react-native-maps --save
  • react-native link react-native-maps

虽然这个依赖显示了高德地图,但是经过这些安装高德地图的试验,我感觉这个依赖很奇怪。(1)没有看到高德地图相关的文件MAMapKit.framework、AMapFoundationKit.framework、AMapSearchKit.framework;(2)没有进行高德key值的配置,个人感觉在react-native-maps/ios/AirMaps/AIRMapManager.m文件中,31行应该是输入高德key值的地方。

react-native-smart-amap

在ios上,可以按照说明文档进行配置,也就是手动链接。我是先执行react-native link react-native-smart-amap进行自动链接,然后再按照说明文件进行配置,需要注意的地方有两点:

  • 拖进项目的RCTAMap.xcodeproj是在\node_modules\react-native-smart-amap\ios\RCTAMap路径下的,不是\node_modules\react-native-smart-amap\RCTAMap下的。
  • “点击在Libraries下已拖进来的RCTAMap.xcodeproj, 选择Build Settings, 找到Framework Search Paths, 将$(SRCROOT)/../../../ios/Frameworks替换成$(SRCROOT)/../../../../ios/Frameworks”这个步骤在每次重装依赖后都要重新执行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import React, { Component } from 'react';
import { StyleSheet, Dimensions, Platform, StatusBar } from 'react-native';
import AMap from 'react-native-smart-amap';
const WINDOW_WIDTH = Dimensions.get('window').width;
const WINDOW_HEIGHT = Dimensions.get('window').height;
const StatusBar_HEIGHT = StatusBar.currentHeight;
class SmartAmap extends Component {
constructor(props) {
super(props);
this.state = {
latitude: 39.889725,
longitude: 116.357357,
};
}
render() {
const options = {
frame: {
width: WINDOW_WIDTH,
height: Platform.OS === 'ios' ? WINDOW_HEIGHT - 55 : WINDOW_HEIGHT - StatusBar_HEIGHT - 55,
},
showsUserLocation: true,
userTrackingMode: Platform.OS === 'ios' ? AMap.constants.userTrackingMode.none : null,
centerCoordinate: {
latitude: this.props.centerCoordinate ? this.props.centerCoordinate.latitude : this.state.latitude,
longitude: this.props.centerCoordinate ? this.props.centerCoordinate.longitude : this.state.longitude,
},
zoomLevel: 18.1,
centerMarker: Platform.OS === 'ios' ? 'icon_location' : 'poi_marker',
};
return (
<AMap
ref={ component => this._amap = component }
style={{...StyleSheet.absoluteFillObject}}
options={options}
radius={100}
/>
);
}
}

这时的运行结果如下图所示,地图上并没有标注出坐标的位置,查了issuse列表看到别也提了这个问题,估计是依赖的问题,我就查了一下依赖的源码,试着找了一下,发现将react-native-smart-amap/ios/RCTAMap/RCTAMap/RCTAMapManager.m文件中422、423、424行的NO改为YES,地图上的位置标注就出来了,如下图所示。

以上都是在ios上的配置,在android端也有需要注意的地方。上一篇React-Native-Android使用高德地图已经讲了在android上使用高德地图,如果想在android端也使用react-native-smart-amap这个依赖来保持一致的话,需要先将android/setting.gradle、android/app/build.gradle、MainApplication.java中关于react-native-amap-android的配置去掉,然后再按照说明文档配置react-native-smart-amap,否则会报两个错误。

第一个错误如下图所示,解决办法是将gradle.properties文件中被注释的org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8放开。

第二个错误如下图所示,是由于包冲突,因为已经存在一个高德地图的依赖了,解决办法就是将android/setting.gradle、android/app/build.gradle、MainApplication.java中关于react-native-amap-android的配置去掉,只保留一个高德地图的配置。

而且android也是存在位置标注不显示的问题,需要将react-native-smart-amap/android/src/main/reactnativecomponent/amap/RCTAMapView.java文件的202行注释放开,地图上的位置标注就出来了,如下图所示。

react-native-smart-amap-location

在ios上,还是执行react-native link react-native-smart-amap-location进行自动链接,然后再按照说明文件进行配置,只有下面这一条需要注意:

  • “点击在Libraries下已拖进来的RCTAMapLocation.xcodeproj, 选择Build Settings, 找到Framework Search Paths, 将$(SRCROOT)/../../../ios/Frameworks替换成$(SRCROOT)/../../../../ios/Frameworks”这个步骤在每次重装依赖后都要重新执行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
import React, { Component } from 'react';
import { View, StyleSheet, Dimensions, Platform, StatusBar, NativeAppEventEmitter, Alert } from 'react-native';
import { Button } from 'antd-mobile';
import AMapLocation from 'react-native-smart-amap-location';
import AppEventListenerEnhance from 'react-native-smart-app-event-listener-enhance';
const WINDOW_WIDTH = Dimensions.get('window').width;
const WINDOW_HEIGHT = Dimensions.get('window').height;
const StatusBar_HEIGHT = StatusBar.currentHeight;
class SmartAmap extends Component {
constructor(props) {
super(props);
this.state = {
latitude: 39.889725,
longitude: 116.357357,
};
this.info = '';
this._onLocationResult = this._onLocationResult.bind(this);
}
componentDidMount() {
let viewAppearCallBack = (event) => {
AMapLocation.init(null); //使用默认定位配置
}
this.addAppEventListener(
this.props.navigator.navigationContext.addListener('didfocus', viewAppearCallBack),
NativeAppEventEmitter.addListener('amap.location.onLocationResult', this._onLocationResult)
);
}
componentWillUnmount () {
//停止并销毁定位服务
AMapLocation.cleanUp()
}
getLocation() {
AMapLocation.getLocation();
}
getReGeocode() {
AMapLocation.getReGeocode();
}
_onLocationResult(result) {
if(result.error) {
Alert.alert(`错误代码: ${result.error.code}, 错误信息: ${result.error.localizedDescription}`)
}
else {
if(result.formattedAddress) {
console.log("定位逆地理编码信息:", result)
Alert.alert(`格式化地址 = ${result.formattedAddress}`);
}
else {
console.log("定位地理编码信息:", result)
Alert.alert(`纬度 = ${result.coordinate.latitude}, 经度 = ${result.coordinate.longitude}`)
}
}
}
render() {
return (
<View>
<Button type="primary" onClick={this.getLocation}>
定位地理编码信息
</Button>
<Button type="primary" onClick={this.getReGeocode}>
定位逆地理编码信息
</Button>
</View>
);
}
}
export default AppEventListenerEnhance(SmartAmap);

React-Native-Android使用高德地图

发表于 2017-03-24   |   分类于 React Native

高德地图key值

  • http://lbs.amap.com/,注册帐号
  • 登录后,进入控制台,应用管理->我的应用,添加新key
    • PackageName为/android/app/src/main/AndroidManifest.xml中package的值
    • 发布版安全码SHA1,在cmd下执行如下命令会生成SHA1值
      cd .android
      keytool -list -v -keystore debug.keystore

显示地图

使用react-native-amap依赖。

  • NPM install

    1
    npm install react-native-amap --save
  • android/setting.gradle:

    1
    2
    include ':react-native-amap-view'
    project(':react-native-amap-view').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-amap-view/android')
  • android/app/build.gradle:

    1
    compile project(":react-native-amap-view")
  • MainApplication.java:

    1
    2
    3
    4
    5
    6
    import com.dianwoba.rctamap.AMapPackage;
    return Arrays.<ReactPackage>asList(
    ...
    , new AMapPackage()
    ...
  • AndroidManifest.xml:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    <!--用于进行网络定位-->
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"></uses-permission>
    <!--用于访问GPS定位-->
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"></uses-permission>
    <!--获取运营商信息,用于支持提供运营商信息相关的接口-->
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"></uses-permission>
    <!--用于访问wifi网络信息,wifi信息会用于进行网络定位-->
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"></uses-permission>
    <!--这个权限用于获取wifi的获取权限,wifi信息会用来进行网络定位-->
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"></uses-permission>
    <!--用于访问网络,网络定位需要上网-->
    <uses-permission android:name="android.permission.INTERNET"></uses-permission>
    <!--用于读取手机当前的状态-->
    <uses-permission android:name="android.permission.READ_PHONE_STATE"></uses-permission>
    <!--写入扩展存储,向扩展卡写入数据,用于写入缓存定位数据-->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>
    <application
    android:name=".MainApplication"
    android:allowBackup="true"
    android:label="@string/app_name"
    android:icon="@mipmap/ic_launcher"
    android:theme="@style/AppTheme">
    <service android:name="com.amap.api.location.APSService"></service>
    <meta-data
    android:name="com.amap.api.v2.apikey"
    android:value="e55ba4f9a563450e0e63f0493f01c323"/><!--高德地图key值-->
    ...
  • Usage

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    import React, { Component } from 'react';
    import { View, Text, TouchableHighlight, StyleSheet, InteractionManager, MapView } from 'react-native';
    import AMapView from 'react-native-amap-view';
    class Amap extends Component {
    render(){
    return (
    <View style={{flex: 1, backgroundColor: '#f00'}}>
    <AMapView initialRegion={{latitude: 30.315888, longitude: 120.165817}} showsUserLocation>
    <AMapView.Marker pinColor="green" draggable title='xxx' description="这是一个好地方" coordinate={{latitude: 30.315888, longitude: 120.165817}} />
    </AMapView>
    </View>
    );
    }
    }
    export default Amap;
  • 启动
    react-native run-android 启动后,报如下错误,将node_modules\react-native-amap-view\android\src\main\java\com\dianwoba\rctamap\RegionChangeEvent.java的17行 改为super(id);就不报这个错了。之后可能需要重启6、7次才能正常启动起来。

地图定位

使用react-native-amap-location 依赖,该依赖仅用于android。

  • NPM install

    1
    npm install react-native-amap-location --save
  • android/setting.gradle:

    1
    2
    include ':reactamaplocation'
    project(':reactamaplocation').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-amap-location')
  • android/app/build.gradle:

    1
    compile project(':reactamaplocation')
  • MainApplication.java:

    1
    2
    3
    4
    5
    6
    import com.xiaobu.amap.AMapLocationReactPackage;
    return Arrays.<ReactPackage>asList(
    ...
    , new AMapLocationReactPackage()
    ...
  • AndroidManifest.xml:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    <!--用于进行网络定位-->
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"></uses-permission>
    <!--用于访问GPS定位-->
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"></uses-permission>
    <!--获取运营商信息,用于支持提供运营商信息相关的接口-->
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"></uses-permission>
    <!--用于访问wifi网络信息,wifi信息会用于进行网络定位-->
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"></uses-permission>
    <!--这个权限用于获取wifi的获取权限,wifi信息会用来进行网络定位-->
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"></uses-permission>
    <!--用于访问网络,网络定位需要上网-->
    <uses-permission android:name="android.permission.INTERNET"></uses-permission>
    <!--用于读取手机当前的状态-->
    <uses-permission android:name="android.permission.READ_PHONE_STATE"></uses-permission>
    <!--写入扩展存储,向扩展卡写入数据,用于写入缓存定位数据-->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>
    <application
    android:name=".MainApplication"
    android:allowBackup="true"
    android:label="@string/app_name"
    android:icon="@mipmap/ic_launcher"
    android:theme="@style/AppTheme">
    <service android:name="com.amap.api.location.APSService"></service>
    <meta-data
    android:name="com.amap.api.v2.apikey"
    android:value="e55ba4f9a563450e0e63f0493f01c323"/><!--高德地图key值-->
    ...
  • Usage

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    import React, { Component } from 'react';
    import { View, Text, TouchableHighlight, StyleSheet, InteractionManager, MapView } from 'react-native';
    import AMapView from 'react-native-amap-view';
    import AMapLocation from 'react-native-amap-location';
    class Amap extends Component {
    constructor(props) {
    super(props);
    this.state = {
    latitude: 39.888396427164594,
    longitude: 116.35112870293183,
    };
    this.clearInterval = null;
    }
    componentDidMount() {
    this.listener = AMapLocation.addEventListener((data) => {
    console.log('data', data);
    this.setState({
    latitude: data.latitude,
    longitude: data.longitude,
    });
    });
    AMapLocation.startLocation({
    accuracy: 'HighAccuracy',
    killProcess: true,
    needDetail: true,
    });
    }
    componentWillUnmount() {
    AMapLocation.stopLocation();
    this.listener.remove();
    }
    render() {
    return (
    <View style={{height: 500, width: 500,}}>
    </View>
    );
    }
    render(){
    return (
    <View style={{flex: 1, backgroundColor: '#f00'}}>
    <AMapView initialRegion={{latitude: this.state.latitude, longitude: this.state.longitude}} showsUserLocation>
    <AMapView.Marker pinColor="green" draggable title='当前位置' description="这是一个好地方" coordinate={{latitude: this.state.latitude, longitude: this.state.longitude}} />
    </AMapView>
    </View>
    );
    }
    }
    export default Amap;

注意,如果是在模拟器上可能由于缺少定位权限报如下错误。

配置签名后key值问题

如果项目中配置了签名,参考RN官网,也就是在android/app/build.gradle中添加了如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
...
android {
...
defaultConfig { ... }
signingConfigs {
release {
storeFile file(MYAPP_RELEASE_STORE_FILE)
storePassword MYAPP_RELEASE_STORE_PASSWORD
keyAlias MYAPP_RELEASE_KEY_ALIAS
keyPassword MYAPP_RELEASE_KEY_PASSWORD
}
}
buildTypes {
release {
...
signingConfig signingConfigs.release
}
}
}
...

这时生成环境的高德key值需要根据签名中的SHA1值生成,打开cmd,切换到签名keystore文件所在目录,输入 keytool -list -v -keystore xxx.keystore命令即可查看SHA1值。由这个SHA1值生成的高德key值在发布环境是好使的。

但是在开发环境会报key值不正确,首先的感觉是SHA1值是生成环境的,那再根据开发环境SHA1值生成key值试一次,打开出cmd,cd .android 进入到 .android下,输入keytool -v -list -keystore debug.keystore命令即可查看开发SHA1值,但仍然报key值不正确。在网上查原因是由于配置了发布签名导致的,需要修改android/app/build.gradle文件让开发环境使用相同的签名配置,这样可以开发和生成环境使用同一个高德key值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
signingConfigs {
release {
storeFile file(MYAPP_RELEASE_STORE_FILE)
storePassword MYAPP_RELEASE_STORE_PASSWORD
keyAlias MYAPP_RELEASE_KEY_ALIAS
keyPassword MYAPP_RELEASE_KEY_PASSWORD
}
debug {
storeFile file(MYAPP_RELEASE_STORE_FILE)
storePassword MYAPP_RELEASE_STORE_PASSWORD
keyAlias MYAPP_RELEASE_KEY_ALIAS
keyPassword MYAPP_RELEASE_KEY_PASSWORD
}
}

React-Native-IOS开发

发表于 2017-03-23   |   分类于 React Native

最近在入门用React Native进行IOS开发,由于开始的时候没有设备,是在Android上开发的,开发出一部分功能后又在IOS上调试的,其中遇到的问题整理如下。

mac常用

快捷键

  • Command+M 最小化当前窗口
  • Command+Option+M 最小化当前应用程序所有窗口
  • Command+H 隐藏除当前应用程序之外所有程序
  • Command+Option+M+H 隐藏所有应用程序窗口
  • Command+Q 关闭当前应用程序
  • Command+S 将模拟器屏幕截图保存到桌面
  • Command+Tab 选中程序,松开Tab,按住Command不放的同时按住Opt,松开Command 将最小化的窗口恢复回来
  • Command+Shift+K Xcode中clean项目
  • Simulator -> window -> Scale 调整模拟器窗口大小

命令

  • xcodebuild -version 查看Xcode版本号

真机运行报错

错误1

需修改一下Bundle Identifier的值。

错误2

需在Xcode的Select a project or target中选择以Tests结尾的项目,然后选择Team。

错误3

1
2
1.BDVRClientSample requires a provisioning profile. Select a provisioning profile for the "Debug" build configuration in the project editor.
2.Code signing is required for product type ‘Application‘ in SDK ‘iOS 10.0‘

需先取消Generate中Automatically manage signing的勾选,然后将Build Settings中Code Signing Identity下的4个值都改为iOS Developer(这之后可能需要关闭Xcode重新打开),在恢复Generate中Automatically manage signing的勾选。

React Native IOS模拟器调试

  • Command+R 刷新
  • Command+D 菜单
  • Shift+Command+H 返回主界面

React Native 兼容问题

  • Text组件,要有backgroundColor,如果只设置color,会连背景都为color的颜色,就看不到字了
  • WingBlank、WhiteSpace组件,背景色为透明色,需要显示设置为白色,否则可能有穿透效果
  • Tabs在IOS需要paddingTop样式,原因如图
  • TabBar的图标非常大,需要添加scale属性,表示图标缩小的比例,如128*128的图标,sacle为4,则显示效果为32*32
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<TabBar
unselectedTintColor="#8a8a8a"
tintColor="#108ee9"
barTintColor="white"
>
<TabBar.Item
icon={{ uri: Icons.task, scale: 4 }}
selectedIcon={{ uri: Icons.taskActive, scale: 4 }}
title="任务"
key="task"
selected={this.state.selectedTab === 'task'}
onPress={this.onPress.bind(this, 'task')}
>
<Tasks navigator={this.props.navigator} />
</TabBar.Item>
</TabBar>
  • 根据平台使用不同样式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const Global = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
...Platform.select({
ios: {
height: WINDOW_HEIGHT-TABBAR_HEIGHT-TABS_HEIGHT-20,
},
android: {
height: WINDOW_HEIGHT-TABBAR_HEIGHT-TABS_HEIGHT-StatusBar_HEIGHT,
},
}),
},
});
  • NetInfo.fetch() 为unknown
1
2
3
4
componentWillMount() {
//如果不侦听网络状态变化,NetInfo.fetch()一直是unknown
NetInfo.isConnected.addEventListener('change', () => {});
}

React Native 连接原生

由于之前是使用Windows在Android上开发的,使用到的依赖
react-native-vector-icons、react-native-image-picker需要与IOS原生连接,此时如果执行react-native link自动连接,担心会影响到已有的Android端的配置,因为Android是进行手动连接的,执行自动连接没有生效,而且也会将不需要连接的依赖一起连接了。所以想按照说明文档进行手动连接,但试过后并没有好使,即使是新初始化的项目,react-native-vector-icons在IOS上进行手动连接也是不好使的。后来我是新初始化了一个同名项目,安装完react-native-vector-icons、react-native-image-picker依赖后进行自动连接,运行好使了,我就把新项目与原项目ios目录中的文件进行了对比,将配置移到原项目中,运行后好使了。

过了两天发现这种方式有点傻,因为那时不知道react-native link可以指定手动连接的依赖。虽然没有试验,但是感觉只需要单独执行react-native link react-native-vector-icons和react-native link react-native-image-picker,再将android目录下变化的文件恢复,应该就可以了。

打包ipa

在Xcode中,修改好Version和Build后,运行设备选择 Generic iOS Device, Product -> archive,等待 Window -> Organizer 弹出,点击Export,Select a method for export 选择 Save for Enterprise Deployment,Device Support 选择 Export one app for all compatible devices,Summary 不勾选,next 导出 ipa。

React-Native-Android原生

发表于 2017-03-18   |   分类于 React Native

创建Android模块

  • React-Native@0.42.0

  • opms/android/app/java/com.opms下,新建MyCustomModule.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.opms;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReactApplicationContext;
public class MyCustomModule extends ReactContextBaseJavaModule {
public MyCustomModule(ReactApplicationContext reactContext) {
super(reactContext);
}
@Override
public String getName() {
return "MyCustomModule";
};
@ReactMethod
public void processString(String input, Callback callback) {
callback.invoke(input.replace("Goodbye", "Hello"));
}
}
  • opms/android/app/java/com.opms下,新建MyCustomReactPackage.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package com.opms;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class MyCustomReactPackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new MyCustomModule(reactContext));
return modules;
}
@Override
public List<Class<? extends JavaScriptModule>> createJSModules() {
return Collections.emptyList();
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Arrays.<ViewManager>asList(
new MyCustomImageManager()
);
}
}
  • opms/android/app/java/com.opms下,修改MainActivity.java文件的getPackages方法
1
2
3
4
5
6
7
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
...
new MyCustomReactPackage()
);
}
  • index.android.js文件中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import React, { Component, PropTypes } from 'react';
import {
NativeModules,
View,
AppRegistry,
} from 'react-native';
export default class opms_rn extends Component {
componentDidMount() {
// RN调Android原生模块
NativeModules.MyCustomModule.processString('Goodbye World.', (text) => {
console.log("原生模块处理后:", text);
});
}
render() {
return (
<View>
<Text>请打开控制台看输出结果</Text>
</View>
);
}
}
AppRegistry.registerComponent('opms', () => opms);
  • 运行结果

创建Android View

  • React-Native@0.42.0

  • opms/android/app/java/com.opms下,新建MyCustomImageManager.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package com.opms;
import android.support.annotation.Nullable;
import android.widget.ImageView;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableNativeArray;
import com.facebook.react.uimanager.ReactStylesDiffMap;
import com.facebook.react.uimanager.SimpleViewManager;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewProps;
import com.facebook.react.uimanager.annotations.ReactProp;
import com.facebook.react.views.image.ImageResizeMode;
import com.facebook.react.views.image.ReactImageView;
import java.util.Arrays;
public class MyCustomImageManager extends SimpleViewManager<ReactImageView> {
public static final String REACT_CLASS = "RCTImageView";
@Override
public String getName() {
return REACT_CLASS;
}
@Override
protected ReactImageView createViewInstance(ThemedReactContext context) {
ReactImageView imageView = new ReactImageView(context, Fresco.newDraweeControllerBuilder(), null);
return imageView;
}
@ReactProp(name = "src")
public void setSrc(ReactImageView view, @Nullable ReadableArray src) {
view.setSource(src);
}
@ReactProp(name = "borderRadius", defaultFloat = 0f)
public void setBorderRadius(ReactImageView view, float borderRadius) {
view.setBorderRadius(borderRadius);
}
@ReactProp(name = ViewProps.RESIZE_MODE)
public void setResizeMode(ReactImageView view, @Nullable String resizeMode) {
view.setScaleType(ImageResizeMode.toScaleType(resizeMode));
}
}
  • opms/android/app/java/com.opms下,新建MyCustomReactPackage.java
1
2
3
4
5
6
7
8
9
public class MyCustomReactPackage implements ReactPackage {
...
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Arrays.<ViewManager>asList(
new MyCustomImageManager()
);
}
}
  • opms/android/app/java/com.opms下,修改MainActivity.java文件的getPackages方法
1
2
3
4
5
6
7
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
...
new MyCustomReactPackage()
);
}
  • 新建ImageView.android.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { PropTypes } from 'react';
import { requireNativeComponent, View } from 'react-native';
var iface = {
name: 'ImageView',
propTypes: {
...View.propTypes, // 包含默认的View的属性
src: PropTypes.string,
borderRadius: PropTypes.number,
resizeMode: PropTypes.oneOf(['cover', 'contain', 'stretch']),
},
};
module.exports = requireNativeComponent('RCTImageView', iface);
  • index.android.js文件中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import React, { Component, PropTypes } from 'react';
import {
View,
AppRegistry,
} from 'react-native';
import ImageView from './ImageView';
export default class opms extends Component {
render() {
return (
<ImageView src="https://facebook.github.io/react/img/logo_og.png" />
);
}
}
AppRegistry.registerComponent('opms', () => opms);
  • 运行结果

当ImageView组件使用到属性src时,很奇怪会报错,所有操作都是按照官方教程做的仍然报错,网上也没有查到原因。

但是如果使用其它属性,如borderRadius,是不会报错的,通过React组件调试工具也能看到属性已经添加上了,应该是已经成功调用了Android原生View。

递归用法

发表于 2017-03-14   |   分类于 JavaScript

前段时间有个刚学编程的人找我做了几道编程题,都用到了递归,让我写个使用递归的总结,我个人用到的递归不多,就这几道编程题总结如下,后期如有新的体会再补充。

适用点

想一下规模为n的问题是否可以经过计算换算成规模为n-1的问题,如阶乘计算,n!=n*(n-1)!,这时就可以用递归。使用递归主要是代码写起来比较方便,大多数情况使用栈和循环都可以替代使用递归。

注意点

写递归算法时,要最先思考边界值,就是递归算法在什么情况下不应该再递归下去,如对于阶乘问题的n为1、0的时候,防止出现死循环。

优化点

用递归算法去解决问题,经常会比较耗性能,因为进行了大量循环,多进行一次递归可能会进行很多次循环,所以控制在哪些条件下无需递归很容易优化性能,如递归求最短路径时,如果当前递归处理的路径长度已经超过了当前最短路径的长度,那就不用再向下递归了。还要会利用数据结构,比如Map能够快速取值,如果是数组取值时需要进行遍历,尽量减少循环的次数。

调试点

递归问题最大的难点可能就在调试上了,因为进行了很多循环,无法跟进每一个循环去看当前的值对不对。这时一定要将问题范围缩小,比如题目要求100个点,可以先拿10个以内的点去调试,这时可以通过断点调试跟进每一次循环,看到所有变量当前的值,这样就容易看出问题出在哪了。如果有问题的标准答案,还要会利用答案,如果解题需要大量循环无法跟进时,可以写一个if条件在满足正确答案时进行输出,也可以在这里打个断点,看看为什么没有获得这个标准的答案,这时比较容易看出问题。

以上都是我做那几个题时的思路和方法,以前我也就写过一两次递归,其实没什么经验,大多数方法都是以前调程序的经验。如果打算深入的往编程这方面发展,有如下建议:

1、要进行编程练习,就像递归,完全自己手动去解决两三个问题也就会用了,也有调试的经验了;
2、一定要会分析问题,拆分问题,这个问题分几步,每一步都做什么,这样比较容易写出程序,容易调试,容易定位问题出在哪步了,其它地方就不用看了,也方便解决问题,问题细化后就容易描述小问题了,可以上网找找现成的例子;
3、一定要会分析现象,现在程序运行的结果不对,是什么样的现象,这个现象表明什么本质,切记不要直接一遍一遍的看代码,找哪里不对,这样很难找到问题的,如果你知道这样写有问题,那当时也就不会这样写了,一定要仔细思考、分析现象,找到问题本质,然后定位有问题的代码范围,再看代码。

React-生命周期

发表于 2017-02-18   |   分类于 React

学了React快一年了,现在用的已经比较顺手了,单个组件的生命周期执行过程已经很清楚了,但是关于多个组件之间怎么进入生命周期方法的还是不确定,现在好好学习一下。

单个组件的生命周期过程

组件的生命周期函数按如下顺序执行,每个方法的说明如下:

  • constructor

    构造函数,对组件的state进行初始化,对函数进行this绑定。

  • componentWillMount

    初始渲染前进入,只执行一次,如果这个函数里调用setState,不会执行渲染操作,而是等这个函数执行完成后再执行初始渲染。如果组件需要从本地存储中读取数据,一般放在这里执行。

  • render

    返回值为页面要显示的组件,组件的重新渲染后触发所有子组件的重新渲染。

  • componentDidMount

    初始渲染完成后进入,只执行一次。如果组件需要从网络侧读取数据,一般放在这里执行。

  • componentWillReceiveProps(nextProps, nextContext)

    组件在初始渲染完成后,当接收到新的props时进入,state发生变化时不会进入,而无论props的内容是否发生变化都会进入。如果在这个函数中调用setState,不会执行渲染操作,而是等该方法执行完成一起渲染。

  • shouldComponentUpdate(nextProps, nextState, nextContext)

    当组件接收到新的state或props时,这个函数将被调用,如果返回false可以阻止组件的重新渲染,也就不会进入本组件的componentWillUpdate、componentDidUpdate方法了。通过这个函数来阻止无必要的重新渲染可以提高程序的性能。

  • componentWillUpdate(nextProps, nextState, nextContext)

    组件在重新渲染前进入,切记不能在这个函数中通过setState更新状态, 如果需要改变,则在componentWillReceiveProps函数中进行改变,否则会导致死循环。

  • componentDidUpdate(prevProps, prevState, prevContext)

    组件在重新渲染完成时进入。

  • componentWillUnmount()

    组件在卸载前进入,如果组件申请了某些资源或订阅了某些消息,需要要在这个函数中释放资源或取消订阅,常见的是在这里清除定时器。

多个组件的生命周期过程

父组件每次执行过setState更新了state后,无论state内容是否发生变化,都会进行所有子孙组件的重新渲染。

先进入父组件的componentWillUnmount,再进入子组件的componentWillUnmount,就像先进入父组件的componentWillUpdate,再进入子组件的componentWillUpdate一样,只是React没有componentDidUnmount方法,看不到是先卸载子组件完成,再卸载父组件完成的。

forceUpdate函数

forceUpdate函数不是组件的生命周期函数,组件通过调用this.forceUpdate()可以强制进行组件的重新渲染,并且会连带所有子组件都重新渲染。通过这种方式的重新渲染,不会进入本组件shouldComponentUpdate方法,会进入子组件的shouldComponentUpdate和componentWillReceiveProps方法及其它生命周期方法。

HOC(High Order Component )高阶组件

参考初识React中的High Order Component
HOC是一个抽象和公用代码的方案,它接受一个包含共用逻辑或者是状态的ReactComponent,并返回一个包含特定逻辑或者是状态的新ReactComponent,相当于对原组件做了一层包装,react-redux中的connect就算是HOC。组件的生命周期执行顺序与父子组件类型的执行顺序一样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
value: '',
};
}
render() {
return (
<input ref="app" value={this.state.value} onChange={this.onChange}/>
);
}
};
const connect = (mapStateFromStore) => (WrappedComponent) => {
class InnerComponent extends React.Component {
constructor(props) {
super(props);
}
render () {console.log(this)
return (
<WrappedComponent ref="middle" />
);
}
}
// 将新组件返回
return InnerComponent;
}
const HOC = connect(() => ({}))(App);
class Root extends React.Component {
render() {console.log(this)
return (
<div>
<HOC ref="hoc"/>
</div>
);
}
}
ReactDOM.render(
<Root />,
document.getElementById('example')
);

《写给大家看的设计书》笔记

发表于 2017-01-18   |   分类于 Reading Notes

最近看了《写给大家看的设计书》,对页面设计的原则有了基本的了解,以下是个人感觉书中比较好的内容,记录下来方便以后查看。

CRAP概念

复杂的设计原理在书中被浓缩为4个词,对比、重复、对齐和对和亲密性,英文首字母缩写为CRAP。

对比(Contrast):对比的基本思想是,要避免页面上的元素太过相似。如果元素(字体、颜色、大小、线宽、形状、空间等)不相同,那就干脆让它们截然不同。要让页面引人注目,对比通常是最重要的一个因素,正是它能使读者首先看这个页面。要想实现有效的对比,对比就必须强烈。

重复(Repetition):让设计中的视觉要素在整个作品中重复出现。可以重复颜色、形状、材质、空间关系、线宽、字体、大小和图片,等等。这样一来,既能增加条理性,还可以加强统一性。不要把重复用的太滥,而应当尽量“采用多样性实现统一”。

对齐(Alignment):任何东西都不能在页面上随意安放。每个元素都应当与页面上的另一个元素有某种视觉联系。这样能建立一种清晰、精巧而且清爽的外观。元素之间会有一条看不见的线把它们连在一起,它会告诉读者,即使这些项并不靠近,但它们属于同一组。

亲密性(Proximity):彼此相关的项应当靠近,归组在一起。如果多个项彼此之间存在很近的亲密性,它们就会成为一个视觉单元,而不是多个孤立的元素。这有助于组织信息,减少混乱,为读者提供清晰的结构。

色轮的使用

  • 类似色(analogous)组合由色轮上彼此相邻的颜色组成。
  • 色轮上相对的颜色为互补色(complement),最佳搭配是一种作为主色,另一种用于强调。
  • 彼此等间距的三种颜色通常会形成一个让人愉快的三色组(triad)。
  • 从色轮的一边选择一种颜色,再在色轮上找不他对面的互补色两侧的颜色,称为分裂互补三色组(split triad)。
  • 单色组合由一种色调及其相应的多种亮色和暗色组成。

小技巧

  • 把所有大写都变为小写,只保留合适的首字母大写,这样能留出更多的空间,使标题更大、更醒目。
  • 把圆角变为直角,可能会使外观更简洁、更突出。
  • 要有意识地注意你是怎样阅读的,视线怎样移动的。
  • 眯起眼睛,如果页面上的项超过3~5个,就要看看哪些孤立的元素可以归在一组了。
  • 居中对齐会创建一种更正式、更稳重的外观,这种外观显得更为中规中矩,但通常也很乏味。
  • 页面上直至用一种文本对齐。
  • 绝对不要在左对齐的正文或缩进的文本上方将标题居中。
  • 去掉Times Roman、Arial/Helvetica和Sand字体。
  • 由于颜色组合而导致发生视觉抖动的原因通常是色质过于接近。
  • 冷色(蓝色、绿色)总趋于后退,而暖色(红色、橙色)是趋近型的。如果要组合暖色和冷色,一定要少用些暖色。
  • 制作logo可以使用分裂互补三色组。
  • 不要对文本或图片的链接使用默认的蓝色。
  • 不要把文本链接放在有边框的粗笨表单元格中。
  • 正文不要用粗体,也不要占据整个页面的宽度。
  • 不要使用荧光背景色,特别是不要使用荧光文字。
  • 不要让用户移动滚动条,特别是不要让表的宽度超过600像素,否则用户在打印页面时会很恼火。
  • Verdan字体是显示Web正文的一个绝好的选择。
  • 有时名人名言使用Decorative字体非常合适。
  • 向右上倾斜的字体产生一种前进的力量,向下倾斜的字体会产生一种反向的力量。
1…131415…17
© 2021 小朱
由 Hexo 强力驱动
主题 - NexT.Pisces