开发者社区> 小凡晓宇> 正文
阿里云
为了无法计算的价值
打开APP
阿里云APP内打开

autojs自定义控件-导航栏

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

牙叔教程 简单易懂

什么是导航栏

在网页中是这样的

image


在android中是这样的

image




导航栏有哪些东西

image


样式

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

  • 纯文字
  • 文字 + 图标

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

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

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

QQ

image


知乎

image


bilibili

image

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

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

<horitzonal>

 <vertical>

  <text>  </text>

  <img>  </img>

 </vertical>

    ... 3到5个菜单

</horitzonal>

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

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

image


动画

我们来看看手机上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>

);


image


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

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

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

如何解决这个问题?

图片的宽高设置多少

这个有两种解决办法,

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

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

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

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

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

image

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


显示四个图标

下一个问题是, 要显示四个图标, 布局是 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>

);

image

看上去差不多达到了预期

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

image


优化代码

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

考虑用<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();

});

image

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

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

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();

}

image

如果想控制动画速度, 可以修改 差值器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;

    };

image


绑定viewpager

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

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

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

image

    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;

        }

    }

);

image


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

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

  • 颜色
  • 宽度
  • 图片宽高
  • 绑定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文档, 最后才是群里问问 --- 牙叔教程

声明

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


微信公众号 牙叔教程

image

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
autojs自定义控件Switch
牙叔教程 简单易懂
82 0
https://xiaochuhe.blog.csdn.net/article/details/122546973
https://xiaochuhe.blog.csdn.net/article/details/122546973
33 0
Spring Boot使用@Async实现异步调用:自定义线程池
Spring Boot使用@Async实现异步调用:自定义线程池
92 0
Qt编写自定义控件39-导航标签
一、前言 在很多菜单导航界面中,当单击了二级菜单或者三级菜单以后,顶部会显示带箭头或者其他标识的导航标签,可以单击该标签快速切换到对应的界面,也作为指示当前处于哪一级菜单下的界面,主要在WEB中大肆流行,在CS架构的项目中也逐渐应用开来,发现现在越来越多的CS开发的程序,都学习和模仿并应用BS架构的程序中好的方面,尤其是UI方面,取长补短,挺好,专业UI设计师的美感比绝大多数程序员的美感要好很多,他们设计出来的效果都是非常棒的,我个人喜欢去UI中国参看学习各种各样的UI设计,看到好的会下载下来,直接搞个拾色器查看颜色,看下人家的颜色配色搭配的多好。
806 0
如何使用travis-ci自动化构建部署GitHub Pages(gitbook)
Github Pages github pages可以当做你或者你的项目的 Websites,那么我们可以知道 GitHub Pages 有两种最基本的用法: 作为你自己(或者组织)的网站或者博客(访问地址示例:http://username.
1194 0
RabbitMQ & SpringBoot 快速入门
MQ全称为Message Queue, 消息队列(MQ)是一种应用程序对应用程序的通信方法。MQ是消费-生产者模型的一个典型的代表,一端往消息队列中不断写入消息,而另一端则可以读取队列中的消息。
1332 0
Tengine开源新特性:如何让HTTPS处理能力轻松翻倍?
Tengine,轻量级Web服务器,基于Nginx进行开发,针对大访问量网站的需求,新增了很多高级功能和特性。比如,Tengine兼容Nginx的所有配置,并且增加了独立进程框架、页面优化、集成了Lua语言进行扩展等很多实用的功能,并在性能方面做了较大的提升。
20950 0
《Programming WPF》翻译 第9章 1.自定义控件基础
原文:《Programming WPF》翻译 第9章 1.自定义控件基础 在写一个自定义控件之前,你需要问的第一个问题是,我真的需要一个自定义控件吗?一个写自定义控件的主要原因是为了用户界面技术专家可以修改控件的外观,但是正如我们在前些章看到的,内容模型和模板意味着这通常是不必要的。
735 0
《Programming WPF》翻译 第9章 3.自定义功能
原文:《Programming WPF》翻译 第9章 3.自定义功能 一旦你挑选好一个基类,你将要为你的控件设计一个API。大部分WPF元素提供属性暴露了多数功能,事件,命令,因为他们从框架中获取广泛的支持,以及易于使用XAML。
742 0
+关注
332
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
低代码开发师(初级)实战教程
立即下载
阿里巴巴DevOps 最佳实践手册
立即下载
冬季实战营第三期:MySQL数据库进阶实战
立即下载