iOS一种动态栅格布局方案,data方式传数组

作者:新葡京简介

在日常开发过程中,我们会遇到一些需要不定期动态改变布局的页面或视图块,下面用张图展示一下:

文章结构

1.http客户端常用的请求方式

  1. multipart/form-data传数组各个终端的实现方式以及后台怎么处理拿到的数据

2.1 web端
2.2 安卓端(OKHTTP)
2.3 苹果端(AFNetworking)
2.4 后台处理拿到的数据

有一类Component充当了容器的作用,它定义了一个背景,然后可以往里面塞任意的内容。这种结构在实际应用中很常见,如下面的Card组件:

新葡京8455 1zdm_home.png我以这张图解释一下需求,图上的几块都是需要显示不同的功能模块,点击的时候也需要跳转到不同页面。这个布局实现很简单,但是如果这个布局需要不定期的更改,比如A换到右边,大小发生变化等不确定因素,我们不可能就发布一个新的版本去修改这个页面。所以大部分人会选择webView来实现,或者事先约定好几种布局格式,由后台来随时改变布局。

一、http请求常用的两种方式post/get


HTTP请求方式有OPTIONS、GET、HEAD、POST、PUT、DELETE、TRACE、CONNECT

具体的http请求方式种类见这篇文章:
HTTP深入浅出 http请求
HTTP协议详解

通常我们用的只有GET、POST
常用的POST请求,按照content-Type来分,又分为 application/x-www-form-urlencodedmultipart/form-dataapplication/json这几种。

具体的content-Type介绍可以看这篇文章:
理解HTTP之Content-Type

get 与post请求方式的区别就不细讲了,这里我们看下HTTP1.1版本协议里面规定的一个标准的HTTP请求格式应该包含的内容:

新葡京8455 2

事实上这一块并占不了整个页面,大部分情况下只是在tableView中嵌套一截这样的需求,我公司项目的实现用的一直是webView来实现,但是这样就会有很多不必要的问题,比如webView的高度计算,如果客户端来计算高度,在一些网络不稳定情况下,webView的资源没有加载完全,高度就会出现偏差,而且webView的加载速度,性能,和js的交互都是很不理想的。(总之,我大原生就是不爱用h5啦)这里其实有两种解决方案

1.1 HTTP请求格式:

请求方法URI协议/版本
请求头(Request Header)
请求正文

例子:

GET/sample.jspHTTP/1.1
Accept:image/gif.image/jpeg,*/*
Accept-Language:zh-cn
Connection:Keep-Alive
Host:localhost
User-Agent:Mozila/4.0(compatible;MSIE5.01;Window NT5.0)
Accept-Encoding:gzip,deflate

username=jinqiao&password=1234

image.png

  • 方案1:和后台约定好几种布局样式,客户端根据后台参数来动态显示。
  • 方案2:这块视图看为一个整体,根据json数据将其分为X个子块,理论上可以根据数据无限分割下去。

1.2 Content-Type几个常见的类型

1.text/html
2.text/plain
3.text/css
4.text/javascript
5.application/x-www-form-urlencoded
6.multipart/form-data
7.application/json
8.application/xml

组件本身是一个不带任何内容的容器,现在我们想往里面添加内容:

优缺点:第一种方案实现简单,而且可以应付日常所需,但是提前约定的格式必定不会太多,不够灵活。第二种方案完全根据数据决定布局,子块可以无限分割下去,布局灵活,但是数据比较复杂。实际上第二种布局是最近公司安卓小哥想出来 一个思路,我和他分别实现了一下,发现效果很好。这里是安卓小哥的简书。

1.2.1 app常用的三种类型:

(1)application/x-www-form-urlencoded
application/x-www-form-urlencoded是常用的表单发包方式,普通的表单提交,或者js发包,默认都是通过这种方式, body以这样的方式传输 name=homeway&key=nokey
如下:

新葡京8455 3

WX20171214-155514@2x.png

(2)multipart/form-data

这里Content-Type告诉我们,发包是以multipart/form-data格式来传输,另外,还有boundary用于分割数据,具体的boundary由客户端自定,在header中以boundary字段返回给服务端。
当文件太长,HTTP无法在一个包之内发送完毕,就需要分割数据,分割成一个一个chunk发送给服务端
那么--用于区分数据快,而后面的数据633e61ebf351484f9124d63ce76d8469新葡京8455,就是标示区分包作用。

新葡京8455 4

WX20171214-160125@2x.png

(3)application/json
在body中参数会以json字符串的形式,传送出去。如下所示。

新葡京8455 5

image.png

新葡京8455 6

下面来和大家说一下具体的实现思路:

二、multipart/form-data传数组各个终端的实现方式以及后台怎么处理拿到的数据


image.png

新葡京8455 7zdm_home.png

2.1 web端

<input type="hidden" name="cars[]" value="Volvo">
<input type="hidden" name="cars[]" value="Saab">
<input type="hidden" name="cars[]" value="Mercedes">

以上代码,可以看出 HTML 传递数组 就是把参数写成数组的形式,进行传递。
最终上传的值会像这样:
How to pass an array within a query string?

新葡京8455 8

image.png

基础的方式可以这么写:

我们还是以这张图为例:1、我们首先定义两个rowitem元素,分别对应行和块的概念。如上图分为两行,A那里代表一整行,下面的四小块代表一整行。分割为row1row2。2、row1:分割为AB两块,水平排列,宽度比2:3

2.2 安卓端(OKHTTP)

这里是 截取的 postman使用数组参数 进行的请求头的参数。
所以 我们只需要在代码中找到 对应的API 就可以了。按照这个区拼接 HTTP的请求。
我这里 是使用的 okHTTP

private void addParams(MultipartBody.Builder builder) {
    if (params != null && !params.isEmpty()) {
        for (String key : params.keySet()) {
            builder.addPart(Headers.of("Content-Disposition", "form-data; name=""   key.replaceAll("\[\d \]", "")   """),
                    RequestBody.create(null, params.get(key)));
        }
    }
}

然后就OK了
html这样写 有可能就是为了拼接这个才这样写的。 在postman中尝试,完美解决。传递过去 因为有 数组的标示[ ] 会把这个强转成数组。

class Card extends Component {
  render () {
    return (
      <div className='card'>
        <div className='card-content'>
          {this.props.content}
        </div>
      </div>
    )
  }
}

ReactDOM.render(
  <Card content={
    <div>
      <h2>props.children和容器类组件</h2>
       <div>开源、免费、专业、简单</div>
      订阅:<input />
    </div>
  } />,
  document.getElementById('root')
)
  • A不用再分,直接显示图片
  • B分为B1、B2两块,垂直排列,高度比1:1
  • B2分为D、E两块,水平排列,宽度比1:1``row2:分割为1:1:1:1的四小块

2.3 苹果端(AFNetworking)

上面有提到根据content-Type有以下三种常用的类型

1.application/x-www-form-urlencoded
2.multipart/form-data
3.application/json

我们再看下AFNetWorking

  (void)postWithURL:(NSString *)url params:(NSDictionary *)params progress:(void (^)(id))progress success:(void (^)(id))success failure:(void (^)(NSError *))failure
{
    AFHTTPSessionManager *mgr = [AFHTTPSessionManager manager];
    mgr.requestSerializer.timeoutInterval = 20.0f;
    NSLog(@"url:%@nparas:%@",url,params);
    [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;

    [mgr POST:url parameters:params progress:^(NSProgress * _Nonnull uploadProgress) {
        progress?progress(uploadProgress):nil;
    } success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
        [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
        success?success(responseObject):nil;
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
        [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
        failure?failure(error):nil;

    }];
}

官方给出来的三个请求类:

新葡京8455 9

WX20171214-163523@2x.png

对应的content-Type类型如上,请求在不指定请求序列化类时默认用的第一种。第三种主要是对xml类型传输方式的支持,感兴趣的朋友可以自己查阅下。

要切换请求content-Typeapplication/json方式很简单,直接上代码。

AFHTTPSessionManager *mgr = [AFHTTPSessionManager manager];
    mgr.requestSerializer.timeoutInterval = 8.0f;
    NSString *token = [faceTokenMannage shareMannager].token;
    mgr.requestSerializer = [AFJSONRequestSerializer serializer];
    [mgr.requestSerializer setValue:token forHTTPHeaderField:@"Authorization"];

通过Card组件传入一个content属性,这个属性可以传入任意的JSX结构。然后Card内部会通过{this.props.content}把内容渲染到页面上。

json数据的结构如下:

2.3.2 multipart/form-data

我们平时主要用来传文件的函数

- (NSURLSessionDataTask *)POST:(NSString *)URLString
                    parameters:(id)parameters
     constructingBodyWithBlock:(void (^)(id <AFMultipartFormData> formData))block
                      progress:(nullable void (^)(NSProgress * _Nonnull))uploadProgress
                       success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
                       failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure

看下内部的实现方式:

新葡京8455 10

点击跳转

新葡京8455 11

再跳转

新葡京8455 12

可以发现这里对数组的处理是类似的,[].
分别用afnet 以 multipart/form-data的方式传纯属组,以及字典数组,抓包采集的结果如下:

纯数组:

[name1,name2]

新葡京8455 13

image.png

字典数组:

[{ID:peo1,name:ni},{ID:peo2,name:ni2}]

新葡京8455 14

抓包结果

新葡京8455 15

image.png

这样显得太丑了,如果Card中还有其他的组件,写起来就比较费劲。
通用写法:

{ "images": [ { "children": [ { "image": "https://i.huim.com/miaoquan/14966511524892.SS2!/both/300x300/unsharp/true", "weight": 2 }, { "children": [ { "image": "https://i.huim.com/miaoquan/14963170206106.jpg!/both/300x300/unsharp/true", "weight": 1 }, { "children": [ { "image": "https://i.huim.com/miaoquan/14968041079523.jpg!/compress/true/both/300x300", "weight": 1 }, { "image": "https://i.huim.com/miaoquan/14968026112335.jpg!/compress/true/both/300x300", "weight": 1 } ], "orientation": "h", "weight": 1 } ], "orientation": "v", "weight": 3 } ], "height": 212, "orientation": "h" }, { "children": [ { "image": "https://i.huim.com/miaoquan/14929203253142.SS2!/both/300x300/unsharp/true", "weight": 1 }, { "image": "https://i.huim.com/contents/14828912708690.jpg!/both/300x300/unsharp/true", "weight": 1 }, { "image": "https://i.huim.com/miaoquan/14968146185109.jpg!/both/300x300/unsharp/true", "weight": 1 }, { "image": "https://i.huim.com/miaoquan/14968145059484.jpg!/both/300x300/unsharp/true", "weight": 1 } ], "height": 138, "orientation": "h" } ]}

2.4 后台处理拿到的数据

@RequestMapping("/uploadImage")
    public @ResponseBody FeedResult uploadImage(HttpServletRequest request, UploadFileForm form) {
        FeedResult f = new FeedResult();
//纯数组
                String[]ids = request.getParameterValues("groups[]");
//数组对象
                String[]ids = request.getParameterValues("groups_id[]");
        String[]names = request.getParameterValues("groups_name[]");
        try {
            f = FileUploadUtil.handleUploadFile(form, true);
            return f;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return f.error("文件上传失败");
        }
    }

参考文献:
四种常见的 POST 提交数据方式

android OKhttp 参数 传递数组类型

class Card extends Component {
  render () {
    return (
      <div className='card'>
        <div className='card-content'>
          {this.props.children}
        </div>
      </div>
    )
  }
}

ReactDOM.render(
  <Card>
    <h2>props.children和容器类组件</h2>
    <div>开源、免费、专业、简单</div>
    订阅:<input />
  </Card>,
  document.getElementById('root')
)

数据说明:images:数组,数组的元素个数决定了整个View需要分为几行row``children:数组,数组元素个数决定了每个row需要分为多少个块item``heightrow的高度orientation:布局方向,vh,分别对应垂直和水平方向image:该子块显示的图片weight:在这一块中的权重比

把props.children打印出来,你可以看到它是个数组:

最重要的是对这个数据的处理,children代表了块,这里用到了递归的思想,只要这一层的数据有children,就需要一直分割下去,直到children为null,停止分割,显示图片。这里的数据层次较深,逻辑需要处理清晰。

新葡京8455 16

不得不说,安卓小哥的当时提出了这个思路吸引到了我,和他分别实现了过后发现十分好用。这刚好解决了我公司项目中遇到的问题,如果衍生出去,其实可以代替很多地方的布局方式。这种布局灵活度很高,完全由后端数据控制,是个很棒的想法。

image.png

下面展示一下我实现的效果图:

React.js 就是把我们嵌套的 JSX 元素一个个都放到数组当中,然后通过 props.children 传给了 Card。

新葡京8455 17FSGridLayoutDemo.gif

这种嵌套的内容成为了 props.children 数组的机制使得我们编写组件变得非常的灵活,我们甚至可以在组件内部把数组中的 JSX 元素安置在不同的地方:

上面的边框线是我方便区分每一块加上的,实际开发可以忽略。若果需要每个子块的跳转信息,可以在image那一层加一个参数。比如我公司是定义了一个页面跳转协议,客户端,前端,后端都是遵循这个协议,我举个例子,加了一个跳转参数"pushurl":"huim://detail?id=1234",我就知道这块需要跳转到商品id为1234的详情页。实现了高度动态化布局和跳转。我也不知道该称之为什么布局,所以暂时称之为栅格化布局

class Layout extends Component {
  render () {
    return (
      <div className='two-cols-layout'>
        <div className='sidebar'>
          {this.props.children[0]}
        </div>
        <div className='main'>
          {this.props.children[1]}
        </div>
      </div>
    )
  }
}

这个布局正如我文章所述,一般是用来实现那种轻量级的页面,我主要是用来替代那种嵌套的webView的,如果太过复杂的页面当然就没有这个必要了。当然可以衍生出去可能会有适合你自己项目的地方。所以,酌情使用吧。

这是一个两列布局组件,嵌套的 JSX 的第一个结构会成为侧边栏,第二个结构会成为内容栏,其余的结构都会被忽略。这样通过这个布局组件,就可以在各个地方高度复用我们的布局。

我写的demo在这里,想要看具体实现的可以前往下载,欢迎提出更好的实现方式。

如果对你有所帮助和启发,不妨点个喜欢

新葡京8455 1868fc9e97e32f8d394891bde8424dfac5.png

本文由新葡京8455发布,转载请注明来源

关键词: