likes
comments
collection
share

UNIAPP开发安卓TV教程

作者站长头像
站长
· 阅读数 11

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

开发难点

  1. 如何方便的开发调试

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

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

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

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

一、如何方便的开发调试

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

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

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

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

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

  <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进行实现如下

<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,需要缓存的页面单独配置

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

更新一下业务代码

组件区域
<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>
    
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,它可以确定

  <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'
  })
});

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

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

{
  "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
    }
  }
}

组件

<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

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

如果要获取启动参数

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');
}
转载自:https://juejin.cn/post/7272348543625445437
评论
请登录