当前位置: 移动技术网 > 移动技术>移动开发>Android > Flutter 侧滑栏及城市选择UI的实现方法

Flutter 侧滑栏及城市选择UI的实现方法

2019年09月06日  | 移动技术网移动技术  | 我要评论

flutter简介

flutter是谷歌的移动ui框架,可以快速在ios和android上构建高质量的原生用户界面。 flutter可以与现有的代码一起工作。在全世界,flutter正在被越来越多的开发者和组织使用,并且flutter是完全免费、开源的。
它也是构建未来的google fuchsia 应用的主要方式。

目前移动市场上很多业务都需要开发android/ios两个端,开发成本比较高. flutter 在跨端上凭借着性能优势关注量,使用度也持续上升.今天给大家分享在去年就写的一个flutter版本的侧滑栏.

实现

先上一张实现效果图


sliderbar 实现

侧边是一个支持手势滑动的sliderbar,一个自定义的statefulwidget.可以观察到,当手势在侧边滑动时,中央显示选中的标签.

布局

一个横向布局,里面放了一个元素。左边标签的容器尽量占满整个屏幕,右边固定宽度的一个列表(里面放需要展示的label),代码如下:

new row(
  mainaxissize: mainaxissize.min,
  crossaxisalignment: crossaxisalignment.start,
  children: <widget>[
  new expanded(
   child: new center(
    child: new text(selectlabel,
     style:
      new textstyle(color: colors.orange, fontsize: 40.0)))),
  slide
  ],
 );

手势数据处理

flutter 提供 手势处理类 gesturedetector,当手势开始滑动是更新中央label显示,停止或者取消时,取消label显示并把对应的数据填充到label上.

new gesturedetector(
  behavior: hittestbehavior.translucent,
  child: slidewidget,
  onpanstart: (event) {
  updatelabel(context, event.globalposition);
  },
  onpandown: (event) {
  updatelabel(context, event.globalposition);
  },
  onverticaldragupdate: (event) {
  updatelabel(context, event.globalposition);
  },
  onpancancel: () {
  setstate(() {
   selectlabel = '';
  });
  },
  onverticaldragend: (event) {
  setstate(() {
   selectlabel = '';
  });
  },
 );

遇到的问题以及解决方法:

  • gesturedetector 监听的手势很多,当注册 onverticaldragupdate 后,onpanupdate 不在回调,解决方法:将onpanupdate逻辑全部移入onverticaldragupdate,
  • onpanup 未监听到手势抬起,解决方法:换用onpancancel,onverticaldragend方法监听

updatelabel,获取具体选中label的index 公式为 index = dy / widgetheight * labellist.length,其中dy 为 以控件起始点y的位置偏移量,widgetheight为高度, labellist.length为label的长度,刷新数据逻辑如下:

void updatelabel(buildcontext context, offset globalposition) {
 var object = globalkey?.currentcontext?.findrenderobject();
 var translation = object?.gettransformto(null)?.gettranslation();

 int index = ((globalposition.dy - translation.y - topmargin) /
   (globalkey.currentcontext.size.height - topmargin) *
   widget.showlist.length)
  .toint();
 if (index < widget.showlist.length && index >= 0) {
  setstate(() {
  selectlabel = widget.showlist[index];
  if (widget.onchangeselect != null) {
   widget.onchangeselect(selectlabel);
  }
  });
 }
 }

其中,获取控件距离屏幕的距离方法为:

var object = globalkey?.currentcontext?.findrenderobject();
 var translation = object?.gettransformto(null)?.gettranslation();

城市选择主界面实现

主布局

采用了flutter 的stack布局(非常类似android framelayout),下层是城市选择页面数据,上层盖了一层sliderbar

new scaffold(
  appbar: getappbar(),
  body: new stack(children: <widget>[
   getshowcontentview(),
   new slidebar(
    citylistutils.labellist, onchangeselect)
  ]));

ui的下层 使用 listview.builder 根据item类型返回不同类型的widget

widget rightcity = new container(
  color: appcolor.white,
  padding: edgeinsets.only(right: 20.0),
  child: new listview.builder(
   controller: scrollcontroller,
   itemcount: citylistutils.citylist.length,
   itembuilder: (listcontext, position) {
    var city = citylistutils.citylist[position];
    if (city is citymodel) {
    return new gesturedetector(
     behavior: hittestbehavior.translucent,
     child: new container(
      decoration: new boxdecoration(
       border: new border.all(
        color: appcolor.bg1, width: 0.5)),
      height: 48.0,
      padding: edgeinsets.only(left: 15.0),
      alignment: alignment.centerleft,
      child: new text(city.name)),
     ontap:selectcity(city));
    } else if (city is citylabel) {
    return new container(
     width: mediaquery.of(context).size.width,
     height: 20.0,
     padding: edgeinsets.only(left: 15.0),
     child: new text(city.keylabel),
     color: appcolor.bg1,
    );
    }
   }));

城市列表数据处理

城市列表的数据格式如下

{"a":[{"name":"澳门","id":"***","fullword":"aomen","first":"am","isshow":"true"}]}

数据解析使用到dart:convert包,调用json.decode(jsonstr)解析的数据为map,在将map转为具体的实体,实体解析工具推荐使用开源工具自动生成模型文件 flutterjsonbeanfactory 得到城市实体的解析model如下:

import 'dart:convert' show json;

class citymodel {
 string first;
 string fullword;
 string id;
 string isshow;
 string name;
 bool isselected = false;

 citymodel.fromparams(
  {this.first, this.fullword, this.id, this.isshow, this.name});

 factory citymodel(jsonstr) => jsonstr is string
  ? citymodel.fromjson(json.decode(jsonstr))
  : citymodel.fromjson(jsonstr);

 citymodel.fromjson(jsonres) {
 first = jsonres['first'];
 fullword = jsonres['fullword'];
 id = jsonres['id'];
 isshow = jsonres['isshow'];
 name = jsonres['name'];
 }

 @override
 string tostring() {
 return '{"first": ${first != null?'${json.encode(first)}':'null'},"fullword": ${fullword != null?'${json.encode(fullword)}':'null'},"id": ${id != null?'${json.encode(id)}':'null'},"isshow": ${isshow != null?'${json.encode(isshow)}':'null'},"name": ${name != null?'${json.encode(name)}':'null'}}';
 }
}

将首字母,城市数据存入citylist里,并将首字母列表传入到sliderbar中,记录字母索引所在的位置

class citylistutils {
 list citylist = [];
 list<string> labellist = [];
 map<string, indexposition> mapkey = {};

 void parse(var map) {
 if (map is string) {
  map = json.decode(map);
 }
 map maplist = map['destination'];
 int index = 0, labelposition = 0;
 maplist.keys.foreach((key) {
  citylist.add(new citylabel(key));
  labellist.add(key);
  mapkey[key] = new indexposition(labelposition, index);
  labelposition++;
  index++;
  for (var value in maplist[key]) {
  index++;
  citylist.add(new citymodel(value));
  }
  ;
 });
 }
}

联动处理

当滑动sliderbar时,应将城市列表滑到对应的位置,listview 提供 scrollcontroller 去为listview 添加监听及 auto scroll listview, 里面对应的有两个方法可以滑动,一个是带有动画 animateto,一个不带有动画的滑动 jumpto,此处使用不带有的方法,传递参数为 滑动的偏移量,实现如下

onchangeselect onchangeselect = (keylabel) {
  indexposition index = citylistutils.mapkey[keylabel];
  scrollcontroller.jumpto(index.total * 48.0 - index.label * 28.0);
  };

其中 onchangeselect定义为

typedef onchangeselect(string keylabel);

使用接口回调的方式将选中的key回传,并使用citylistutils里存储的mapkey找到对应的首字母索引,计算出listview应该滑动的偏移量

遇到的问题

计算的偏移量不准,导致滑动不能准确定位到首字母索引上。

原因:item 使用 container布局 高度未限制,手动获取到的高度不准确

解决方法:使用固定的item高度

总结

以上所述是小编给大家介绍的flutter 侧滑栏及城市选择ui的实现方法,希望对大家有所帮助

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网