autojs自定义控件-导航栏

简介: 目前自定义控件可以控制的属性:● 颜色● 宽度● 图片宽高● 绑定viewpagerrecyclerview的高度是由 文字 + 图片 控制的, 所以就不用设置recyclerview的高度

牙叔教程 简单易懂

什么是导航栏

在网页中是这样的


在android中是这样的




导航栏有哪些东西


样式

如果要给用户选择的话, 那么我们必须考虑两种样式

  • 纯文字
  • 文字 + 图标

文字加图标还有方向上的选择

  • 文字上图标下
  • 文字左图标右

至于选择哪种方向, 我们看看自己手机上的app都是怎么做的

QQ


知乎


bilibili

那么显而易见, 我们也选择图标在上, 文字在下这种风格

一般底部是3到5个菜单, 那么整体的布局就是

<horitzonal>

<vertical>

<text>  </text>

<img>  </img>

</vertical>

   ... 3到5个菜单

</horitzonal>

这个数量不固定的, 肯定是要动态修改的

布局的话, 大家都是类似网页的 移动端flex布局justify-content对齐方式: space-around


动画

我们来看看手机上app的动画

QQ

文字和图标变色, 且图标会有缩放动效

淘宝

文字和图标变色, 无动效

bilibili

文字和图标变色, 图标由平静到微笑, 或者平视到瞪眼

总的来说, 除了变色, 没有很明显的动效

那么, 这个教程是为了做动画而做动画, 所以我们加一些明显的平移和缩放效果, 设置可以先隐藏图标, 选中之后再显示出来

开始撸代码

"ui";

ui.layout(

   <horizontal>

       <vertical>

           <img src="file://./img/首页.png"></img>

           <text>首页</text>

       </vertical>

       <vertical>

           <img src="file://./img/发现.png"></img>

           <text>发现</text>

       </vertical>

       <vertical>

           <img src="file://./img/收藏.png"></img>

           <text>收藏</text>

       </vertical>

       <vertical>

           <img src="file://./img/通知.png"></img>

           <text>通知</text>

       </vertical>

   </horizontal>

);



这问题就来了, 明明应该显示4个, 却只看见三个,

最大的问题是, 我们要给所有手机显示相同, 至少是相似的效果,

问题是所有手机像素不一样啊, 那家伙, 手机是各种各样的宽高,

如何解决这个问题?

图片的宽高设置多少

这个有两种解决办法,

  • 用具体的像素数值
  • 用比重设置布局, 也就是 layout_weight

这里我们采用第一种, 用具体的像素数值, 步骤如下:

首先确定导航栏的高度是多少, 他应该有一个最小值, 一个最大值,

因为手机有大屏幕, 也有小屏幕, 这样我们就得多写一些代码了,

为了少写点代码, 我选择一个固定值56dp

这个高度看着就差不多了,


显示四个图标

下一个问题是, 要显示四个图标, 布局是 space-around,

这就要求我们知道宽度的具体数值, 前提是知道父控件的宽度,

既然我们做的是自定义控件, 那么控件的宽度的具体数值就交给用户设置,

用户设置宽度之后, 我们再计算子控件的宽度

现在假设用户设置的是屏幕的宽度, 同时图片的宽度也是用户设置, 假设是32dp

let dw = device.width;

let config = {

   父控件宽度: dw,

   图片的宽度: "32dp",

   子控件数量: 4,

};

config.图片的宽度 = yashuUtil.dp2px(parseInt(config.图片的宽度));

log("config.图片的宽度 = " + config.图片的宽度); // 48

let spaceWidth = (config.父控件宽度 - config.图片的宽度 * config.子控件数量) / config.子控件数量;

log("spaceWidth: " + spaceWidth); // 87

let halfSpaceWidth = Math.floor(spaceWidth / 2 + 0.5);

log("halfSpaceWidth = " + halfSpaceWidth); // 44


ui.layout(

   <vertical>

       <horizontal h="56dp">

           <vertical bg="#ff00ff" w="{{config.图片的宽度}}px" marginLeft="{{halfSpaceWidth}}px" marginRight="{{halfSpaceWidth}}px">

               <img src="file://./img/首页.png" h="{{config.图片的宽度}}px"></img>

               <text bg="#00ff00">首页</text>

           </vertical>

           <vertical bg="#f000ff" w="{{config.图片的宽度}}px" marginLeft="{{halfSpaceWidth}}px" marginRight="{{halfSpaceWidth}}px">

               <img src="file://./img/发现.png" h="{{config.图片的宽度}}px"></img>

               <text>发现</text>

           </vertical>

           <vertical w="{{config.图片的宽度}}px" marginLeft="{{halfSpaceWidth}}px" marginRight="{{halfSpaceWidth}}px">

               <img src="file://./img/收藏.png" h="{{config.图片的宽度}}px"></img>

               <text>收藏</text>

           </vertical>

           <vertical w="{{config.图片的宽度}}px" marginLeft="{{halfSpaceWidth}}px" marginRight="{{halfSpaceWidth}}px">

               <img src="file://./img/通知.png" h="{{config.图片的宽度}}px"></img>

               <text>通知</text>

           </vertical>

       </horizontal>

   </vertical>

);

看上去差不多达到了预期

文字加个居中, 图片和文字加点空白距离


优化代码

上面四个控件都是一模一样的结构

考虑用<grid>改造一下

ui.layout(

   <vertical>

       <grid id="buttons" spanCount="4" h="56dp">

           <vertical bg="#ff00ff" w="{{config.图片的宽度}}px" marginLeft="{{halfSpaceWidth}}px" marginRight="{{halfSpaceWidth}}px">

               <img src="{{src}}" h="{{config.图片的宽度}}px"></img>

               <text text="{{text}}" margin="2" w="*" gravity="center" bg="#00ff00"></text>

           </vertical>

       </grid>

   </vertical>

);

增加点击事件

我们就先来个最基础的变色

grid增加点击事件还是很简单的

ui.buttons.on("item_click", function (item, i, itemView, listView) {

   var len = items.length;

   for (var i = 0; i < len; i++) {

       let item = items[i];

       if (item.selected) {

           item.selected = false;

           break;

       }

   }

   itemView.text.attr("textColor", config.未选中颜色);

   itemView.img.attr("tint", config.未选中颜色);

   item.selected = true;

   ui.buttons.adapter.notifyDataSetChanged();

});

这个单单变色的效果有点单调, 我们加个图片缩放

图片缩放这个用到的是属性动画

function 动效(view) {

   let animatorSet = new AnimatorSet(); //多个动画 动画集

   animatorSet.setDuration(300);

   let scaleX = ObjectAnimator.ofFloat(view, "scaleX", 1, 0.5, 1);

   let scaleY = ObjectAnimator.ofFloat(view, "scaleY", 1, 0.5, 1);

   animatorSet.play(scaleX).with(scaleY);

   animatorSet.start();

}

如果想控制动画速度, 可以修改 差值器TimeInterpolator 和 估值器TypeEvaluator

更好的动效, 就需要自行设计啦

到底为止, 我们的导航栏基本就搞完了, 接下来, 我们把它封装为自定义控件

自定义控件

前面我们主要是用普通UI的方式, 来打草稿, 现在我们要改成自定义控件了

自定义控件的基本方法可以看这篇教程

autojs自定义组件 https://www.yuque.com/yashujs/bfug6u/rbho2n

我们的组件是给用户使用的, 那么需要开放那些接口呢?

  • 组件的宽高
  • 图片的宽高
  • 绑定的图片和文字数据
  • 导航栏, 一般和viewpager绑定, 所以再加一个 setViewpager 方法
  • 用户一般都要改颜色, setColor

最基本的就这些, 导航栏, 也没多少东西

自定义控件render遇到的问题

render是这样子的

MyView.prototype.render = function () {

   return (

       <grid id="buttons" spanCount="4" h="{{gridW}}">

           <vertical w="{{imgW}}px" marginLeft="{{halfSpaceWidth}}px" marginRight="{{halfSpaceWidth}}px">

               <img id="img" tint="{{selected?config.选中颜色:config.未选中颜色}}" src="{{src}}" h="{{图片的宽度}}px"></img>

               <text id="text" textColor="{{selected?config.选中颜色:config.未选中颜色}}" text="{{text}}" margin="2" w="*" gravity="center"></text>

           </vertical>

       </grid>

   );

};

render里面的xml只能访问UI作用域中的变量, 这个也不是什么大问题, 我们可以使用

runtime.ui.bindingContext[key] = value;

把非UI作用域中的变量, 添加到UI作用域中;

但是, 考虑到, 每个自定义控件的实例, 各自的私有属性需要是独立的,

比如说布局中, 有多个自定义控件Q, Q控件的每个宽度是不一样的,

这个就实现不了了,  

不过, 我们可以不用这种render方式, 换别的方式实现, 有两种方案:

  • 重写grid的bind方法
  • 不使用grid

我们先用第一种, 能用则用, 不能用再换别的方法


经过测试, 第一种方式是可行的

自定义控件的render方法

只写一个recyclerview标签, 其他啥都没有;

autojs的<grid>就是封装的recyclerview

   MyView.prototype.render = function () {

       // runtime.ui.bindingContext["that_h"] = this.h;

       return <androidx.recyclerview.widget.RecyclerView></androidx.recyclerview.widget.RecyclerView>;

   };

onViewCreated 方法

做了两个操作, 设置布局管理器和设置adapter

   MyView.prototype.onViewCreated = function (view) {

       //设置布局管理器

       let layoutManager = new LinearLayoutManager(context);

       view.setLayoutManager(layoutManager);


       let recycleAdapter = createRecyclerViewAdapter(this);

       view.setAdapter(recycleAdapter);

   };

onFinishInflation 方法

修改了recyclerview的宽和背景色

   MyView.prototype.onFinishInflation = function (view) {

       // 不带单位, 默认dp

       view.attr("w", "" + this.w);

       view.attr("bg", "" + this.bg);

   };

那么recyclerview的子控件的属性在哪里修改呢?

那肯定是重写recyclerview的 onBindViewHolder 方法

onCreateViewHolder 方法

修改子控件的宽度

       onCreateViewHolder: function (parent, viewType) {

           // 视图创建

           let view;

           let holder;

           view = ui.inflate(itemLayout, parent, false);

           view.attr("w", scope.itemW);

           holder = JavaAdapter(RecyclerView.ViewHolder, {}, view);

         ...

onBindViewHolder

修改子控件图片和文字的宽高

也许有人会问, 为什么上面修改了view的宽度, 这里修改了view.img的宽高, 为啥不放一起?

我觉得能用就行, 如果你很纠结的话, 可以自己研究研究

       onBindViewHolder: function (holder, position) {

           // 数据绑定

           let view = holder.itemView;

           let item = items[position];

           view.text.setText(item.text);

           view.img.attr("h", "" + scope.itemW);

           view.img.attr("w", "" + scope.itemW);

           let halfSpaceWidth = scope.getHalfSpaceWidth();

           halfSpaceWidth = dp2px(halfSpaceWidth);

           setMargins(view, halfSpaceWidth, 8, halfSpaceWidth, 8);

           view.img.attr("src", item.src);

         ...

新增方法: setItems

绑定recyclerview的新数据

   MyView.prototype.setItems = function (items) {

       this.items = items;


       // 设置布局管理器

       let view = this.view;

       let layoutManager = new GridLayoutManager(activity, this.items.length);

       view.setLayoutManager(layoutManager);

       let recycleAdapter = createRecyclerViewAdapter(this);

       view.setAdapter(recycleAdapter);

   };

修改颜色的方法 setColor

   MyView.prototype.setColor = function (selectedColor, unselectedColor) {

       this.selectedColor = selectedColor;

       this.unselectedColor = unselectedColor;

   };


绑定viewpager

绑定后, 需要监听viewpager的页面选择事件, 并同时修改导航栏的状态,

并且, 点击导航栏, 同时viewpager也要对应改变,

这个改变viewpager的动作是在导航栏的点击事件中添加的

   MyView.prototype.setViewpager = function (viewpager) {

       this.viewpager = viewpager;

       let that = this;

       // 为ViewPager添加页面改变事件

       viewpager.addOnPageChangeListener({

           onPageSelected: function (position) {

               // 将当前的页面对应的底部标签设为选中状态

               let items = that.items;

               var len = items.length;

               for (var i = 0; i < len; i++) {

                   let item = items[i];

                   if (item.selected) {

                       item.selected = false;

                       break;

                   }

               }


               let item = items[position];

               item.selected = true;

               that.view.adapter.notifyDataSetChanged();

           },

       });

   };

调用新增方法

ui.myView.widget.setItems(items);

ui.myView.widget.setColor("#c44db0", "#4db0c4");

ui.myView.widget.setViewpager(ui.viewpager);


自定义组件的宽高设置

这是通过 defineAttr 方法设置的

下面这个是设置自定义控件的宽度, 注意属性的名字是mw, 不可以是w, 因为w已经被autojs占用了;

只要不是被内部占用的名字都可以, aw, bw, ww, www, abc都可以使用,

这里沿用java的member的意思

this.defineAttr(

   "mw",

   (view, name, defaultGetter) => {

       return this._w;

   },

   (view, attr, value, defineSetter) => {

       log("defineSetter: w: value = " + value);

       if (~value.indexOf("px")) {

           this._w = px2dp(parseInt(value.replace("px", "")));

       } else {

           this._w = +value;

       }

   }

);


到这一步, 我们的自定义控件基本就写完了;

目前自定义控件可以控制的属性:

  • 颜色
  • 宽度
  • 图片宽高
  • 绑定viewpager

recyclerview的高度是由 文字 + 图片 控制的, 所以就不用设置recyclerview的高度

环境

手机:小米11pro
MIUI: 13.0.12
Android版本: 12
Autojs版本: 9.2.5

雷电: 4.0.63
Android版本: 7
Autojs版本: 8.8.20

名人名言

思路是最重要的, 其他的百度, bing, stackoverflow, github, 安卓文档, autojs文档, 最后才是群里问问 --- 牙叔教程

声明

部分内容来自网络 本教程仅用于学习, 禁止用于其他用途


微信公众号 牙叔教程

相关文章
|
3月前
|
容器
uniapp中制作侧边导航栏
uniapp中制作侧边导航栏
390 0
|
移动开发
Qt自定义控件之仪表盘的完整实现
Qt自定义控件之仪表盘的完整实现
|
Android开发
autojs最近任务多界面
牙叔教程 简单易懂
624 0
autojs普通版控制台美化
autojs普通版控制台美化
826 0
|
Android开发
autojs按钮不可点击
牙叔教程 简单易懂
1034 0
|
3月前
|
XML Java Android开发
Android Studio App开发之实现底部标签栏BottomNavigationView和自定义标签按钮实战(附源码 超详细必看)
Android Studio App开发之实现底部标签栏BottomNavigationView和自定义标签按钮实战(附源码 超详细必看)
361 0
|
3月前
|
C++
C++ Qt开发:自定义Dialog对话框组件
在之前的文章中笔者已经为大家展示了默认`Dialog`组件的使用方法,虽然内置组件支持对数据的输入,但有时候我们需要一次性输入多个数据,此时如果之使用默认模态对话框似乎不太够用,此时我们需要自己创建一个自定义对话框,需要说明的是此类对话框也是一种窗体,所以可以在其上面放置任何通用组件,以实现更多复杂的开发需求。自定义对话框需要解决的问题是,如何让父窗体与子窗体进行数据交换,要实现数据的交换有两种方式,第一种方式是通过动态加载模态对话框,当用户点击确定后通过`GetValue()`来拿到数据,而第二种方式则是通过发送信号的方式将数据投递给父窗体,这两种方式都可以,读者可根据自身需求来选择不同的通
66 1
C++ Qt开发:自定义Dialog对话框组件
|
iOS开发
iOS开发-导航栏标题动画
iOS开发-导航栏标题动画
182 0
iOS开发-导航栏标题动画
从零开始学Pyqt5之【控件介绍】(17):菜单栏QMenuBar、QToolBar工具栏、QStatusBar状态栏
从零开始学Pyqt5之【控件介绍】(17):菜单栏QMenuBar、QToolBar工具栏、QStatusBar状态栏
从零开始学Pyqt5之【控件介绍】(17):菜单栏QMenuBar、QToolBar工具栏、QStatusBar状态栏
|
Android开发
autojs控制台美化
牙叔教程 简单易学 使用场景 自定义控制台
707 0