UNIAPP开发安卓TV教程

目前开发安卓TV的方法相对开说是十分的少的,使用uniapp开发相对来说几乎是没有的,因此,写下这篇文章供大家参考。

开发难点

  1. 如何方便的开发调试

  2. 如何使需要被聚焦的元素获取聚焦状态

  3. 如何是被聚焦的元素滚动到视图中心位置

  4. 如何缓存切换路由的时,上一个页面的聚焦状态,再回来是还是聚焦状态

  5. 如何启用wgt和apk两种方式的升级

一、如何方便的开发调试

之前我在论坛看到人家说,没办法呀,电脑搬到电视,然后调试。

其实大可不必,安装android studio里边创建一个模拟器就可以了。

注意:最好安装和电视系统相同的版本号,我这里是长虹电视,安卓9所以使使用安卓9的sdk

二、如何使需要被聚焦的元素获取聚焦状态

uniapp的本质上是webview, 因此我们可以在它的元素上添加tabIndex, 就可以获取焦点了。

xml 复制代码
  <view class="card" tabindex="0">    <image :src="`${VITE_URL}${props.image}`" fade-show lazy-load mode="aspectFill"></image>    <view class="bottom">      <text class="name">{{ props.name }}</text>      <text class="remark">{{ props.remark }}</text>      <div class="footer">        <view class="tags">          <text class="tag" v-for="tag in tags" :key="tag">{{ tag }}</text>        </view>        <text class="price">&yen; {{ props.price }}</text>      </div>    </view>  </view>


.card {  border-radius: 1.25vw;  overflow: hidden;}.card:focus {  box-shadow: 0 0 0 0.3vw #fff, 0 0 1vw 0.3vw #333;  outline: none;  transform: scale(1.03);  transition: box-shadow 0.3s ease, transform 0.3s ease;}

三、如何是被聚焦的元素滚动到视图中心位置

使用renderjs进行实现如下

ruby 复制代码
<script module="homePage" lang="renderjs">export default {   mounted(){   let isScrolling = false; // 添加一个标志位,表示是否正在滚动   document.body.addEventListener("focusin", (e) => {    if (!isScrolling) { // 检查是否正在滚动        isScrolling = true; // 设置滚动标志为true        requestAnimationFrame(() => {          // @ts-ignore            e.target.scrollIntoView({                behavior: 'smooth',                // @ts-ignore                block:e.target.dataset.index? "end":'center'            });            isScrolling = false; // 在滚动完成后设置滚动标志为false        });    }   }); }}</script>

就可以使被聚焦元素滚动到视图中心,requestAnimationFrame的作用是缓存

四、如何缓存切换路由的时,上一个页面的聚焦状态,再回来是还是聚焦状态

通过设置tabindex属性为0和1,会有不同的效果:

  1. tabindex="0":将元素设为可聚焦,并按照其在文档中的位置来确定焦点顺序。当使用Tab键进行键盘导航时,tabindex="0"的元素会按照它们在源代码中的顺序获取焦点。这可以用于将某些非交互性元素(如

    、等)设为可聚焦元素,使其能够被键盘导航。

  2. tabindex="1":将元素设为可聚焦,并将其置于默认的焦点顺序之前。当使用Tab键进行键盘导航时,tabindex="1"的元素会在默认的焦点顺序之前获取焦点。这通常用于重置焦点顺序,或者将某些特定的元素(如重要的输入字段或操作按钮)置于首位。

需要注意的是,如果给多个元素都设置了tabindex属性,那么它们的焦点顺序将取决于它们的tabindex值,数值越小的元素将优先获取焦点。如果多个元素具有相同的tabindex值,则它们将按照它们在文档中的位置来确定焦点顺序。同时,负数的tabindex值也是有效的,它们将优先于零和正数值获取焦点。

我们要安装缓存插件,如pinia或vuex,需要缓存的页面单独配置

javascript 复制代码
import { defineStore } from "pinia";export const useGlobalStore = defineStore("global", {  state: () => ({    home_active_tag: "active0",    hot_active_tag: "hot0",    dish_active_tag: "dish0",  }),});

更新一下业务代码

typescript 复制代码
组件区域
<template>  <view class="card" :tabindex="home_active_tag === 'packagecard' + props.id ? 1 : 0">    <image :src="`${VITE_URL}${props.image}`" fade-show lazy-load mode="aspectFill"></image>    <view class="bottom">      <text class="name">{{ props.name }}</text>      <text class="remark">{{ props.remark }}</text>      <div class="footer">        <view class="tags">          <text class="tag" v-for="tag in tags" :key="tag">{{ tag }}</text>        </view>        <text class="price">&yen; {{ props.price }}</text>      </div>    </view>  </view></template>

const { home_active_tag } = storeToRefs(useGlobalStore());


页面区域

   <view class="content">      <FoodCard        v-for="_package in list.dishes"        @click="goShopByFood(_package)"        :id="_package.id"        :name="_package.name"        :image="_package.image"        :tags="_package.tags"        :price="_package.price"        :shop_name="_package.shop_name"        :shop_id="_package.shop_id"        :key="_package.id"      ></FoodCard>      <image class="card" @click="goMore" :tabindex="home_active_tag === 'more' ? 1 : 0" style="width: 29.375vw; height: 25.9375vw" src="/static/home/more.png" mode="aspectFill" />    </view>

const goShopByFood = async (row: Record<string, any>) => {  useGlobalStore().home_active_tag = "foodcard" + row.id;  uni.navigateTo({    url: `/pages/shop/index?shop_id=${row.shop_id}`,    animationDuration: 500,    animationType: "zoom-fade-out",  });};

如果,要设置启动默认焦点 id和index可默认设置,推荐启动第一个焦点组用index,它可以确定

arduino 复制代码
    <view class="active">      <image        v-for="(active, i) in list.active"        :key="active.id"        @click="goActive(active, i)"        :tabindex="home_active_tag === 'active' + i ? 1 : 0"        :src="`${VITE_URL}${active.image}`"        data-index="0"        fade-show        lazy-load        mode="aspectFill"        class="card"      ></image>    </view>

import { defineStore } from "pinia";export const useGlobalStore = defineStore("global", {  state: () => ({    home_active_tag: "active0",  默认选择    hot_active_tag: "hot0",    dish_active_tag: "dish0",  }),});

对于多层级的,要注意销毁,在前往之前设置默认焦点

typescript 复制代码
const goHot = (index: number) => {  useGlobalStore().home_active_tag = "hotcard" + index;  useGlobalStore().hot_active_tag = "hot0";  uni.navigateTo({    url: `/pages/hot/index?index=${index}`,    animationDuration: 500,    animationType: "zoom-fade-out",  });};

五、如何启用wgt和apk两种方式的升级

pages.json

json 复制代码
 {      "path": "components/update/index",      "style": {        "disableScroll": true,        "backgroundColor": "#0068d0",        "app-plus": {          "backgroundColorTop": "transparent",          "background": "transparent",          "titleNView": false,          "scrollIndicator": false,          "popGesture": "none",          "animationType": "fade-in",          "animationDuration": 200        }      }    }

组件

xml 复制代码
<template>  <view class="update">    <view class="content">      <view class="content-top">        <text class="content-top-text">发现版本</text>        <image class="content-top" style="top: 0" width="100%" height="100%" src="@/static/bg_top.png"> </image>      </view>      <text class="message"> {{ message }} </text>      <view class="progress-box">        <progress class="progress" border-radius="35" :percent="progress.progress" activeColor="#3DA7FF" show-info stroke-width="10" />        <view class="progress-text">          <text>安装包正在下载,请稍后,系统会自动重启</text>          <text>{{ progress.totalBytesWritten }}MB/{{ progress.totalBytesExpectedToWrite }}MB</text>        </view>      </view>    </view>  </view></template><script setup lang="ts">import { onLoad } from "@dcloudio/uni-app";import { reactive, ref } from "vue";const message = ref("");const progress = reactive({ progress: 0, totalBytesExpectedToWrite: "0", totalBytesWritten: "0" });onLoad((query: any) => {  message.value = query.content;  const downloadTask = uni.downloadFile({    url: `${import.meta.env.VITE_URL}/${query.url}`,    success(downloadResult) {      plus.runtime.install(        downloadResult.tempFilePath,        {          force: false,        },        () => {          plus.runtime.restart();        },        (e) => {}      );    },  });  downloadTask.onProgressUpdate((res) => {    progress.progress = res.progress;    progress.totalBytesExpectedToWrite = (res.totalBytesExpectedToWrite / Math.pow(1024, 2)).toFixed(2);    progress.totalBytesWritten = (res.totalBytesWritten / Math.pow(1024, 2)).toFixed(2);  });});</script><style lang="less">page {  background: transparent;  .update {    /* #ifndef APP-NVUE */    display: flex;    /* #endif */    justify-content: center;    align-items: center;    position: fixed;    left: 0;    top: 0;    right: 0;    bottom: 0;    background-color: rgba(0, 0, 0, 0.65);    .content {      position: relative;      top: 0;      width: 50vw;      height: 50vh;      background-color: #fff;      box-sizing: border-box;      padding: 0 50rpx;      font-family: Source Han Sans CN;      border-radius: 2vw;      .content-top {        position: absolute;        top: -5vw;        left: 0;        image {          width: 50vw;          height: 30vh;        }        .content-top-text {          width: 50vw;          top: 6.6vw;          left: 3vw;          font-size: 3.8vw;          font-weight: bold;          color: #f8f8fa;          position: absolute;          z-index: 1;        }      }    }    .message {      position: absolute;      top: 15vw;      font-size: 2.5vw;    }    .progress-box {      position: absolute;      width: 45vw;      top: 20vw;      .progress {        width: 90%;        border-radius: 35px;      }      .progress-text {        margin-top: 1vw;        font-size: 1.5vw;      }    }  }}</style>

App.vue

vbnet 复制代码
<script setup lang="ts">import { onLaunch } from "@dcloudio/uni-app";import { useRequest } from "./hooks/useRequest";import dayjs from "dayjs";onLaunch(() => {  // #ifdef APP-PLUS  plus.runtime.getProperty("", async (app) => {    const res: any = await useRequest("GET", "/api/tv/app");    if (res.code === 2000 && res.row.version > (app.version as string)) {      uni.navigateTo({        url: `/components/update/index?url=${res.row.url}&type=${res.row.type}&content=${res.row.content}`,        fail: (err) => {          console.error("更新弹框跳转失败", err);        },      });    }  });  // #endif});</script>

如果要获取启动参数

arduino 复制代码
  plus.android.importClass("android.content.Intent");  const MainActivity = plus.android.runtimeMainActivity();  const Intent = MainActivity.getIntent();  const roomCode = Intent.getStringExtra("roomCode");  if (roomCode) {    uni.setStorageSync("roomCode", roomCode);  } else if (!uni.getStorageSync("roomCode") && !roomCode) {    uni.setStorageSync("roomCode", "8888");  }
相关推荐
zwjapple4 小时前
docker-compose一键部署全栈项目。springboot后端,react前端
前端·spring boot·docker
像风一样自由20206 小时前
HTML与JavaScript:构建动态交互式Web页面的基石
前端·javascript·html
aiprtem7 小时前
基于Flutter的web登录设计
前端·flutter
浪裡遊7 小时前
React Hooks全面解析:从基础到高级的实用指南
开发语言·前端·javascript·react.js·node.js·ecmascript·php
why技术7 小时前
Stack Overflow,轰然倒下!
前端·人工智能·后端
幽络源小助理7 小时前
SpringBoot基于Mysql的商业辅助决策系统设计与实现
java·vue.js·spring boot·后端·mysql·spring
GISer_Jing7 小时前
0704-0706上海,又聚上了
前端·新浪微博
止观止7 小时前
深入探索 pnpm:高效磁盘利用与灵活的包管理解决方案
前端·pnpm·前端工程化·包管理器
whale fall7 小时前
npm install安装的node_modules是什么
前端·npm·node.js
烛阴7 小时前
简单入门Python装饰器
前端·python