牙叔教程 简单易懂
效果展示
注意, 对话是一句一句出来的,
并且, 底部的新增对话, 向上滚动的时候, 是可以设置滚动速度的,
这是重写了RecyclerView的滚动方法.
![](https://img.haomeiwen.com/i17665846/6d0b2b501ad2af24.gif)
视频演示
这是个多张图片合成后的视频效果, 一秒钟换一次图片,
ffmpeg可以调整图片显示的时间
https://www.bilibili.com/video/bv1uP4y1V7YK
段子视频化步骤
- 找个段子, 最好是对话形式的
- 把对话生成图片
- 把图片合成视频
备注: 所有操作均在手机上进行, ffmpeg使用的是autojs的ffmpeg插件.
环境
手机: Mi 11 Pro
Android版本: 11
Autojs版本: 9.0.11
ffmpeg版本: v4.4-dev-416
你将学到以下知识点
- 使用ffmpeg把图片合成视频
- RecyclerView的基本使用
- 一个聊天界面模板
- 沉浸式通知栏
- recyclerView更新数据
- recyclerView控制滚动速度
- 重写layoutManager的某些方法
- recyclerView条目的多种布局方法
- GradientDrawable的使用方法
代码讲解
1. 停止其他脚本
调试必备, 因为我们会多次调试脚本, 那么就需要停止之前的脚本, 不然运行的脚本一多, 一会就autojs就挂了, 或者出现别的bug
engines.all().map((ScriptEngine) => {
if (engines.myEngine().toString() !== ScriptEngine.toString()) {
ScriptEngine.forceStop();
}
});
2. 导入类
注意有的类开头是Packages.
importClass(android.graphics.drawable.GradientDrawable);
importClass(android.view.WindowManager);
importClass(android.view.View);
importClass(Packages.androidx.recyclerview.widget.LinearSmoothScroller);
importClass(android.graphics.Color);
importClass(Packages.androidx.recyclerview.widget.LinearLayoutManager);
importClass(Packages.androidx.recyclerview.widget.RecyclerView);
3. 计算通知栏高度
const resources = context.getResources();
const status_bar_height = resources.getDimensionPixelSize(
resources.getIdentifier("status_bar_height", "dimen", "android")
);
4. UI界面
注意在xml中的注释格式
ui.layout(
<vertical>
{/*标题*/}
<horizontal>
<img></img>
<text>段子视频化</text>
<img></img>
</horizontal>
<View></View>
{/*聊天内容*/}
<androidx.recyclerview.widget.RecyclerView
></androidx.recyclerview.widget.RecyclerView>
{/*底栏*/}
<horizontal>
<img></img>
<View></View>
<img></img>
<img></img>
</horizontal>
</vertical>
);
5. 读取头像
注意在exit事件中, 增加图片回收, 避免内存泄露
let leftAvatarPath = files.path("./leftAvatar.jpg");
let rightAvatarPath = files.path("./rightAvatar.jpg");
let leftAvatar = images.read(leftAvatarPath);
let rightAvatar = images.read(rightAvatarPath);
let leftAvatarBitmap = leftAvatar.bitmap;
let rightAvatarBitmap = rightAvatar.bitmap;
events.on("exit", function () {
leftAvatar.recycle();
rightAvatar.recycle();
});
6. 格式化段子
格式为
right:我的意中人是个盖世英雄
right:有一天他会踩着七色云彩来娶我
right:我猜中了开头
right:可是我猜不到这结局
left:尘世间最痛苦的事莫过于此
left:我会跟那个女孩说我爱她
left:如果非要把这份爱加上一个期限
left:我希望是一万年
...
~line.indexOf("left:") 这句话的意思是left在line中,
!~line.indexOf("left:") 这句话的意思是left不在line中,
还挺好记的吧, 感叹号就表示否定的意思, 没有感叹号就表示肯定.
let jokeContent = files.read("/sdcard/joke.js");
jokeContent = jokeContent.trim();
let lines = jokeContent.split("\n");
var len = lines.length;
let dataList = [];
for (var i = 0; i < len; i++) {
let line = lines[i];
if (~line.indexOf("left:")) {
dataList.push({
avatar: leftAvatarBitmap,
content: line.replace("left:", ""),
direction: "left",
});
} else if (~line.indexOf("right:")) {
dataList.push({
avatar: rightAvatarBitmap,
content: line.replace("right:", ""),
direction: "right",
});
}
}
dataList的元素有三个属性, 头像, 对话, 方向,
7. 点击立即开始
这是因为截图的时候, 系统会有弹框, 所以要用一个多线程点击弹框
threads.start(function () {
let view = text("立即开始").visibleToUser(true).boundsInside(0, 0, device.width, device.height).findOne();
view.click();
});
8. 生成对话, 并截图
对话是一句一句出现的, 不可能一下子把所有对话都展现出来, 否则就叫聊天记录, 而不叫对话了;
对话一句一句出现以后, recyclerView要刷新数据,
如果对话到了UI界面的底部, 那么还需要滚动到视线之内,
最好是慢慢滚动到视线内, 而不是一瞬间就出现,
因此, 需要重写smoothScrollToPosition方法, 来控制滚动速度.
setInterval(() => {
let position = newDataList.length;
ui.run(function () {
recyclerView.adapter.notifyItemInserted(position - 1);
recyclerView.smoothScrollToPosition(position - 1);
});
if (imgNum < 10) {
imgNum = "0" + imgNum;
}
log(imgNum); // 00-28
images.captureScreen("/sdcard/" + imgNum + ".png");
imgNum++;
}, 1000);
9. recyclerView设置adapter
layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
recyclerView.setLayoutManager(layoutManager);
let recycleAdapter = createRecyclerViewAdapter(newDataList);
recyclerView.setAdapter(recycleAdapter);
10. 对话左右不同的布局
let leftBoxXml = (
<horizontal margin="0 15 0 0">
<img id="img" marginRight="8" w="45dp" h="45dp" circle="true"></img>
<text id="content" textColor="#cc000000" textSize="17sp" padding="11" layout_height="wrap_content"></text>
</horizontal>
);
let rightBoxXml = (
<horizontal margin="0 15 0 0" layout_width="match_parent" layout_height="wrap_content" gravity="right">
<text id="content" textColor="#cc000000" textSize="17sp" padding="11" marginRight="8"></text>
<img id="img" w="45dp" h="45dp" circle="true"></img>
</horizontal>
);
11. 创建RecyclerView.Adapter
对没了解过RecyclerView的人来说, 有点难,
对于经常使用rv的人来说, 最重要的一句是view.content.setMaxWidth(maxWidth);
这是控制view的最大宽度, 这样, 如果对话内容太长, 也可以避免覆盖头像;
viewType, 左右布局就是依据他来区分的;
onCreateViewHolder: 生成布局;
onBindViewHolder: 给布局绑定数据;
getItemCountr: 数据的数量;
getItemViewType: 布局类型, 当前是左右, 另外经常使用的是head, foot
return RecyclerView.Adapter({
onCreateViewHolder: function (parent, viewType) {
let view;
let holder;
if (viewType === 0) {
view = ui.inflate(leftBoxXml, parent, false);
view.content.setMaxWidth(maxWidth);
setBackgroundRoundRounded(view.content);
holder = JavaAdapter(RecyclerView.ViewHolder, {}, view);
} else {
view = ui.inflate(rightBoxXml, parent, false);
view.content.setMaxWidth(maxWidth);
setBackgroundRoundRounded(view.content, "#96ec69");
holder = JavaAdapter(RecyclerView.ViewHolder, {}, view);
}
return holder;
},
onBindViewHolder: function (holder, position) {
let data = dataList[position];
holder.itemView.img.setImageBitmap(data.avatar);
holder.itemView.content.setText(data.content);
},
getItemCount: function () {
return dataList.length;
},
getItemViewType: function (position) {
return dataList[position].direction === "left" ? 0 : 1;
},
});
12. 多张图片合成视频
let cmd = " -f concat -safe 0 -i /sdcard/input.txt -vsync vfr -pix_fmt yuv420p /sdcard/output1.mp4";
log(ffmpeg.inProcess.exec(cmd));
app.viewFile("/sdcard/output1.mp4");
名人名言
思路是最重要的, 其他的百度, bing, stackoverflow, github, 安卓文档, autojs文档, 最后才是群里问问
--- 牙叔教程
声明
部分内容来自网络
本教程仅用于学习, 禁止用于其他用途
网友评论