欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

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

程序员文章站 2022-04-09 13:42:52
flutter简介 flutter是谷歌的移动ui框架,可以快速在ios和android上构建高质量的原生用户界面。 flutter可以与现有的代码一起工作。在全世界,f...

flutter简介

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

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

实现

先上一张实现效果图

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

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的实现方法,希望对大家有所帮助