autojs模仿QQ長按彈窗菜單實現(xiàn)示例詳解二
引言
上一節(jié)講了列表和長按事件
彈窗菜單
由粗到細, 自頂向下的寫代碼
我們現(xiàn)在要修改的文件是showMenuWindow.js
function showMenuWindow(view) { let popMenuWindow = ui.inflateXml( view.getContext(), ` <column> <button id="btn1" text="btn1" /> </column> `, null ); let mPopWindow = new PopupWindow(popMenuWindow, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, true); mPopWindow.setOutsideTouchable(true); mPopWindow.showAsDropDown(view); } module.exports = showMenuWindow;
我們先修改xml, QQ的彈窗由兩部分組成
- 菜單列表
- 箭頭
因此, xml如下
<column> <androidx.recyclerview.widget.RecyclerView id="recyclerView" padding="16" layout_width="match_parent" layout_height="match_parent"> </androidx.recyclerview.widget.RecyclerView> <android.view.View id='arrow' ></android.view.View> </column>
這給菜單我們用的也是recyclerView, 因此先設(shè)置他的adapter, 如果不會就看上一節(jié)課程;
function showMenuWindow(view) { let popMenuWindow = ui.inflateXml( ... ); setPopMenuRecyclerViewAdapter(popMenuWindow.recyclerView, []); ... }
設(shè)置Adapter的時候, 第一個參數(shù)我們是有的, 第二個參數(shù)是adapter要綁定的數(shù)據(jù), 現(xiàn)在沒有;
這給菜單數(shù)據(jù)應(yīng)該有哪些屬性呢?
- 菜單顯示的文字
- 菜單點后的回調(diào)函數(shù)
因此, 數(shù)據(jù)大概是這樣的
menus: [ { name: "復制", handle: () => { console.log("復制"); }, }, { name: "分享", handle: () => { console.log("分享"); }, }, ],
這種可配置的數(shù)據(jù), 我們把它放到config.js中.
數(shù)據(jù)有了, 接下來我們進入setPopMenuRecyclerViewAdapter方法內(nèi)部,
提醒一下, 我是復制黏貼的上一節(jié)課的setAdapter方法, 因此設(shè)置recyclerview的方法大差不差.
setPopMenuRecyclerViewAdapter.js
let definedClass = false; const PopMenuRecyclerViewViewHolder = require("./PopMenuRecyclerViewViewHolder"); const PopMenuRecyclerViewAdapter = require("./PopMenuRecyclerViewAdapter"); const showMenuWindow = require("../showMenuWindow.js"); module.exports = async function (recyclerView, items) { if (!definedClass) { await $java.defineClass(PopMenuRecyclerViewViewHolder); await $java.defineClass(PopMenuRecyclerViewAdapter); definedClass = true; } var adapter = new PopMenuRecyclerViewAdapter(items); adapter.setLongClick(showMenuWindow); recyclerView.setAdapter(adapter); };
基本上就是復制黏貼, 修改一下類名即可
PopMenuRecyclerViewAdapter.js中, 修改一下holderXml即可
PopMenuRecyclerViewViewHolder.js, bind需要修改
bind(item) { this.itemView.attr("text", item); this.item = item; }
除了設(shè)置adapter, 菜單彈框還需要設(shè)置layoutManager, 這樣我們可以控制水平方向上菜單的數(shù)量
const layoutManager = new androidx.recyclerview.widget.GridLayoutManager(this, 5); grid.setLayoutManager(layoutManager);
先設(shè)置layoutManager, 再設(shè)置adapter
PopMenuRecyclerViewViewHolder.js, 需要修改一下bind方法, 他的item是對象, 文本是item.name
bind(item) { this.itemView.attr("text", item.name); this.item = item; }
運行代碼, 看看效果
菜單出來了, 接著寫箭頭, 菜單的xml是
<column> <androidx.recyclerview.widget.RecyclerView id="recyclerView" padding="16" layout_width="match_parent" layout_height="match_parent"> </androidx.recyclerview.widget.RecyclerView> <android.view.View id='arrow' ></android.view.View> </column>
下面那個View就是我們放箭頭的地方
箭頭
箭頭可能指向上方, 也可能指向下方, 我們通過設(shè)置View的前景, 來展示箭頭
arrowView.setForeground(drawable);
這里我們要寫自己的drawable, 因此, 要繼承
class TriangleDrawable extends android.graphics.drawable.Drawable {}
重寫他的draw方法
draw(canvas) { canvas.drawPath(this.path, paint); }
畫筆創(chuàng)建一支就好, 因為沒有發(fā)現(xiàn)要創(chuàng)建多支畫筆的需求, 以后需要再改, 滿足當下即可;
path肯定夠是變的, 因為箭頭有上下兩個位置;
那么在這個TriangleDrawable類中, 我們要實現(xiàn)那些東西呢?
- 設(shè)置箭頭方向 setDirection
- 目前想不到別的了
如何確認箭頭方向?
假設(shè)列表有ABC三條數(shù)據(jù), ABC依次排列, 在A的頂部, 如果有控件繼續(xù)放置一條數(shù)據(jù)D的話,
那么我們就把彈框菜單放到A的頂部, 如果沒有, 就放到A的底部
怎么判斷是否有足夠的空間放下D數(shù)據(jù)呢? 和那些東西有關(guān)?
- 被長按的view的頂部坐標
- 彈框菜單的高度
有這兩個信息, 我們就可以判斷箭頭的方向了.
為了判斷箭頭方向, 我們新建一個文件, getArrowDirection.js, 文件夾名popMenuRecyclerView, 和箭頭明顯不合適, 因此我們新建文件夾popMenuArrow
被長按的view的頂部坐標
view.getTop()
彈框菜單的高度, 因為彈框還沒有顯示出來, 所以我們要預先測量他的高度
popWindow.getContentView().measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); let popupWindowHeight = popWindow.getContentView().getMeasuredHeight()
判斷箭頭指向
if (longClickedViewTop - popupWindowHeight < 0) { // 上面放不下了, 菜單在下面出現(xiàn), 箭頭指向上方 return "up"; } else { return "down"; }
我們給箭頭一個背景色, 先看當前的效果
可以看到箭頭上下的效果已經(jīng)出來了,
箭頭View的挪動使用了addView和removeView
let arrowView = popMenuWindow.findView("arrow"); popMenuWindow.findView("root").removeView(arrowView); popMenuWindow.findView("root").addView(arrowView, 0);
這里有個問題, 箭頭的背景色為什么那么長, 是彈框菜單的兩倍多.
這是因為GridLayoutManager第二個參數(shù)設(shè)置了5, 我們改為Math.min, 取最小值, 寬度問題就符合預期了
const layoutManager = new GridLayoutManager(view.getContext(), Math.min(popMenus.length, 5));
調(diào)整popwindow的位置
如果彈框菜單在長按控件的上方, 那么應(yīng)該偏移多少?
Y軸偏移量 = 彈框菜單的高度 + 長按控件的高度
調(diào)用方法如下
let offset = popMenuCalculateOffset(view, mPopWindow, arrowDirection); if (arrowDirection == "down") { console.log("箭頭朝下"); mPopWindow.showAsDropDown(view, offset.x, offset.y); }
我們新建一個文件 popMenuCalculateOffset.js
module.exports = function popMenuCalculateOffset(longClickedView, popWindow, arrowDirection) { let contentView = popWindow.getContentView(); let width = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); let height = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); contentView.measure(width, height); popWindow.setBackgroundDrawable(new ColorDrawable(0)); let contentViewHeight = contentView.getMeasuredHeight(); let longClickedViewHeight = longClickedView.getHeight(); console.log("contentViewHeight = " + contentViewHeight); if (arrowDirection == "down") { let y = contentViewHeight + longClickedViewHeight; return { x: 0, y: -y }; } else { return { x: 0, y: 0 }; } };
獲取高寬高以后, 我們的
let offset = popMenuCalculateOffset(view, mPopWindow, arrowDirection); if (arrowDirection == "down") { console.log("箭頭朝下"); mPopWindow.showAsDropDown(view, offset.x, offset.y); } else { let arrowView = popMenuWindow.findView("arrow"); popMenuWindow.findView("root").removeView(arrowView); popMenuWindow.findView("root").addView(arrowView, 0); mPopWindow.showAsDropDown(view, offset.x, offset.y); }
代碼寫了不少了, 看看效果, 及時排查bug
箭頭朝上
箭頭朝下
繪制箭頭
我們用canvas畫個三角形, 首先我們要繼承類, 重寫他的draw方法
class TriangleDrawable extends android.graphics.drawable.Drawable {}
單獨寫一個類文件 TriangleDrawable.js, 放到文件夾 popMenuArrow;
繪制箭頭之前, 要知道箭頭的寬高, 和箭頭的中點;
- 箭頭的寬高, 我們就用arrowView的高度;
- 箭頭的中點, 我們指向被長按的控件 X 軸的中心
為了使類, 盡可能的比較純, 我們傳遞的參數(shù)選擇具體的數(shù)值, 而不是控件;
這里的純指的是沒有副作用, 以及可復用的程度
class TriangleDrawable extends android.graphics.drawable.Drawable { setHeight(height) { this.height = height; } setWidth(width) { this.width = width; } setDirection(direction) { this.direction = direction; } setColor(color) { this.color = Color.parse(color).value; } setLongClickedViewWidth(longClickedViewWidth) { this.longClickedViewWidth = longClickedViewWidth; } draw(canvas) { trianglePath.reset(); if (this.direction == "down") { console.log("down"); trianglePath.moveTo(this.width / 2, this.height); trianglePath.lineTo(this.width / 2 - this.height / 2, 0); trianglePath.lineTo(this.width / 2 + this.height / 2, 0); } else { trianglePath.moveTo(this.width / 2, 0); trianglePath.lineTo(this.width / 2 - this.height / 2, this.height); trianglePath.lineTo(this.width / 2 + this.height / 2, this.height); } trianglePath.close(); canvas.drawPath(trianglePath, paint); } } module.exports = TriangleDrawable;
在popupWindow出現(xiàn)之前, 我們要把箭頭繪制出來,
await setArrowForeground(arrow, arrowDirection, view); mPopWindow.showAsDropDown(view, offset.x, offset.y);
使用onPreDraw, 在繪制之前, 我們可以獲取到正確的寬高
arrow.getViewTreeObserver().addOnPreDrawListener( new android.view.ViewTreeObserver.OnPreDrawListener({ onPreDraw: function () { arrow.getViewTreeObserver().removeOnPreDrawListener(this); let arrowHeight = arrow.getHeight(); let arrowWidth = arrow.getWidth(); triangleDrawable.setWidth(arrowWidth); triangleDrawable.setHeight(arrowHeight); arrow.setForeground(triangleDrawable); return true; }, }) );
代碼寫了不少了, 先測試一下效果
箭頭朝上
箭頭朝下
修改顏色和圓角
顏色這個就不多說了, 非常容易修改, 說下圓角
修改圓角是在這個文件中: showMenuWindow.js, 我們要給RecyclerView包裹一層card
<card cardCornerRadius="8dp" w='wrap_content'> ... </card>
給彈框菜單添加點擊事件
也就是給彈框菜單中的recyclerview添加點擊事件
增加點擊事件所在的文件是 popMenuRecyclerView/PopMenuRecyclerViewAdapter.js,
我們修改他的onCreateViewHolder
onCreateViewHolder(parent) { let testRecyclerViewViewHolder = new PopMenuRecyclerViewViewHolder(ui.inflateXml(parent.getContext(), holderXml, parent)); testRecyclerViewViewHolder.itemView.setOnClickListener(() => { let item = this.data[testRecyclerViewViewHolder.getAdapterPosition()]; item.handle(); return true; }); return testRecyclerViewViewHolder; }
點擊事件生效了, 還有個問題, 點擊了之后,彈框菜單沒有消失, 我們在這里又引用不到彈框?qū)嵗? 怎么弄?
彈框菜單點擊事件引用彈框?qū)嵗?/h2>
我們可以用全局對象, 掛載彈框的實例;
我們不選怎全局對象, 而是去能引用的地方引用實例;
在 showMenuWindow.js 這個文件中, 出現(xiàn)了popupWindow實例, 我們把這個實例作為參數(shù), 傳遞給
setPopMenuRecyclerViewAdapter
setPopMenuRecyclerViewAdapter(mPopWindow, grid, popMenus);
setPopMenuRecyclerViewAdapter.js
module.exports = async function (mPopWindow, recyclerView, items) { const menuClick = (item, itemView) => { console.log(itemView); item.handle(); mPopWindow.dismiss(); }; var adapter = new PopMenuRecyclerViewAdapter(items); adapter.setClick(menuClick); recyclerView.setAdapter(adapter); };
我們在這個文件中給adapter設(shè)置了點擊事件, 相應(yīng)的要在 PopMenuRecyclerViewAdapter.js 文件中添加方法,
setClick
class PopMenuRecyclerViewAdapter extends androidx.recyclerview.widget.RecyclerView.Adapter { constructor(data) { super(); this.data = data; this.click = () => {}; } onCreateViewHolder(parent) { let testRecyclerViewViewHolder = new PopMenuRecyclerViewViewHolder(ui.inflateXml(parent.getContext(), holderXml, parent)); testRecyclerViewViewHolder.itemView.setOnClickListener(() => { let item = this.data[testRecyclerViewViewHolder.getAdapterPosition()]; this.click(item, testRecyclerViewViewHolder.itemView); return true; }); return testRecyclerViewViewHolder; } ... setClick(click) { this.click = click; } } module.exports = PopMenuRecyclerViewAdapter;
到這里就模仿的差不多了, 差不多就行.
如果要增加多個菜單, 在config.js中修改配置即可
環(huán)境
設(shè)備: 小米11pro
Android版本: 12
Autojs版本: 9.3.11
名人名言
思路是最重要的, 其他的百度, bing, stackoverflow, github, 安卓文檔, autojs文檔, 最后才是群里問問 --- 牙叔教程
聲明
部分內(nèi)容來自網(wǎng)絡(luò) 本教程僅用于學習, 禁止用于其他用途
以上就是autojs模仿QQ長按彈窗菜單實現(xiàn)示例詳解二的詳細內(nèi)容,更多關(guān)于autojs模仿QQ長按彈窗菜單的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android的OkHttp包中的HTTP攔截器Interceptor用法示例
攔截器是OkHttp處理HTTP請求方面所具有的一個強大特性,這里我們就來看一下Android的OkHttp包中的HTTP攔截器Interceptor用法示例,需要的朋友可以參考下2016-07-07Android控件ViewPager實現(xiàn)帶有動畫的引導頁
這篇文章主要為大家詳細介紹了Android控件ViewPager實現(xiàn)帶有動畫的引導頁,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-05-05Flutter使用AnimationController實現(xiàn)控制動畫
這篇文章主要想帶大家來嘗試一下Flutter如何使用AnimationController實現(xiàn)一個拖拽圖片,然后返回原點的動畫,感興趣的可以了解一下2023-05-05Android使用URLConnection提交請求的實現(xiàn)
這篇文章主要為大家詳細介紹了Android使用URLConnection提交請求的實現(xiàn),具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-10-10Android APT 實現(xiàn)控件注入框架SqInject的示例
這篇文章主要介紹了Android APT 實現(xiàn)控件注入框架SqInject的示例,幫助大家更好的理解和學習使用Android,感興趣的朋友可以了解下2021-03-03Android如何監(jiān)測文件夾內(nèi)容變化詳解
最近在開發(fā)android應(yīng)用程序的時候遇到了一個監(jiān)測文件夾的功能,所以下面這篇文章主要給大家介紹了關(guān)于Android如何監(jiān)測文件夾內(nèi)容變化的相關(guān)資料,需要的朋友可以參考下2021-12-12