项目实训—基于AI的智能视频剪辑器(一)项目架构搭建


前言

本次项目实训目标打造一款智能视频剪辑APP,所提供的基本功能为:由用户选择本地视频进行上传,并且提供希望剪辑的主要人物图像,系统将智能检测出人物所在视频片段,并且合成人物cut视频,用户自行选择下载。

在本次实训中,我所负责的部分是移动端代码的编程,本周主要完成的工作是项目基本架构的搭建。


一、项目架构搭建

1.项目目录结构

项目意图打造一款 Android 端app,移动端目录结构基本如下:
在这里插入图片描述

  • activity 目录:主要活动的集合,项目预计采用XPage框架,将以少量主要的Activity控制多个Fragment页面
  • adapter 目录:适配器类集合,用于连接后端数据和前端显示
  • core 目录:核心类的集合,包括一些基类以及框架的核心类
  • fragment 目录:嵌入Activity的UI片段的集合
  • utils 目录:工具类的集合
  • widget 目录:widget小控件类的集合
  • MyApp:Application类
  • res 目录:存放定义应用程序菜单资源的XML文件
  • AndroidManifest.xml:整个Android项目的配置文件,注册四大组件,为应用程序添加权限声明

2. 主要框架引入

项目中主要使用了X系列的框架,包括:

XUI一个简洁的Android原生UI框架,包括UI组件、UI主题样式、UI资源和UI辅助工具
XPage一个方便的fragment页面框架,以通用的Activity作为壳,Fragment作为页面填充展示,并且能够像Activity那样自由的切换和数据交互
XRouter一个轻量级的Android路由框架,基于ARouter上进行改良,优化Fragment的使用,可结合XPage使用
XHttp2一个功能强大的网络请求库,使用RxJava2 + Retrofit2 + OKHttp组合进行封装。支持多种Http请求方式,提供大量丰富的网络请求拦截器
XUtil一个方便实用的Android工具类库。收录了Android开发过程中常用的工具类,并进行简单的分类,易于查询使用
XUpdate一个轻量级、高可用性的Android版本更新框架
XAOP一个轻量级的AOP(面向切片编程)应用框架

创建 x-library.gradle 文件,以引入X系列框架,并将X-Library依赖添加到 build.gradle 文件中

apply plugin: 'com.xuexiang.xaop' //引用XAOP插件
apply plugin: 'com.xuexiang.xrouter' //引用XRouter-plugin插件实现自动注册

//自动添加依赖
configurations.each { configuration ->
    def dependencies = getProject().dependencies
    if (configuration.name == "implementation") {
        //为Project加入X-Library依赖
        //XUI框架
        configuration.dependencies.add(dependencies.create(deps.xlibrary.xui))
        configuration.dependencies.add(dependencies.create(deps.androidx.appcompat))
        configuration.dependencies.add(dependencies.create(deps.androidx.recyclerview))
        configuration.dependencies.add(dependencies.create(deps.androidx.design))
        configuration.dependencies.add(dependencies.create(deps.glide))
        //XUtil工具类
        configuration.dependencies.add(dependencies.create(deps.xlibrary.xutil_core))
        //XAOP切片
        configuration.dependencies.add(dependencies.create(deps.xlibrary.xaop_runtime))
        //XUpdate版本更新
        configuration.dependencies.add(dependencies.create(deps.xlibrary.xupdate))
        //XHttp2
        configuration.dependencies.add(dependencies.create(deps.xlibrary.xhttp2))
        configuration.dependencies.add(dependencies.create(deps.rxjava2))
        configuration.dependencies.add(dependencies.create(deps.rxandroid))
        configuration.dependencies.add(dependencies.create(deps.okhttp3))
        configuration.dependencies.add(dependencies.create(deps.gson))
        //XPage
        configuration.dependencies.add(dependencies.create(deps.xlibrary.xpage_lib))
        //页面路由
        configuration.dependencies.add(dependencies.create(deps.xlibrary.xrouter_runtime))
    }

    if (configuration.name == "annotationProcessor") {
        //XPage
        configuration.dependencies.add(dependencies.create(deps.xlibrary.xpage_compiler))
        //页面路由
        configuration.dependencies.add(dependencies.create(deps.xlibrary.xrouter_compiler))
    }

    if (configuration.name == "debugImplementation") {
        //内存泄漏监测leak
        configuration.dependencies.add(dependencies.create(deps.leakcanary))
    }
}

configurations.all {
    resolutionStrategy.force deps.okhttp3
}

在 util 文件夹下,创建 XBasicLibInit.java 文件,来完成X系列基础库的初始化,在 MyApp.java 中调用 init 函数,使得项目运行即可初始化基础库

public final class XBasicLibInit {

    private XBasicLibInit() {
        throw new UnsupportedOperationException("u can't instantiate me...");
    }

    /**
     * 初始化基础库SDK
     */
    public static void init(Application application) {
        //工具类
        initXUtil(application);

        //网络请求框架
        initXHttp2(application);

        //页面框架
        initXPage(application);

        //切片框架
        initXAOP(application);

        //UI框架
        initXUI(application);

        //路由框架
        initRouter(application);
    }

    /**
     * 初始化XUtil工具类
     */
    private static void initXUtil(Application application) {
        XUtil.init(application);
        XUtil.debug(MyApp.isDebug());
        TokenUtils.init(application);
    }

    /**
     * 初始化XHttp2
     */
    private static void initXHttp2(Application application) {
        //初始化网络请求框架,必须首先执行
        XHttpSDK.init(application);
        //需要调试的时候执行
        if (MyApp.isDebug()) {
            XHttpSDK.debug();
        }
//        XHttpSDK.debug(new CustomLoggingInterceptor()); //设置自定义的日志打印拦截器
        //设置网络请求的全局基础地址
        XHttpSDK.setBaseUrl("https://gitee.com/");
//        //设置动态参数添加拦截器
//        XHttpSDK.addInterceptor(new CustomDynamicInterceptor());
//        //请求失效校验拦截器
//        XHttpSDK.addInterceptor(new CustomExpiredInterceptor());
    }

    /**
     * 初始化XPage页面框架
     */
    private static void initXPage(Application application) {
        PageConfig.getInstance()
                .debug(MyApp.isDebug())
                .setContainActivityClazz(BaseActivity.class)
                .init(application);
    }

    /**
     * 初始化XAOP
     */
    private static void initXAOP(Application application) {
        XAOP.init(application);
        XAOP.debug(MyApp.isDebug());
        //设置动态申请权限切片 申请权限被拒绝的事件响应监听
        XAOP.setOnPermissionDeniedListener(permissionsDenied -> XToastUtils.error("权限申请被拒绝:" + StringUtils.listToString(permissionsDenied, ",")));
    }

    /**
     * 初始化XUI框架
     */
    private static void initXUI(Application application) {
        XUI.init(application);
        XUI.debug(MyApp.isDebug());
    }

    /**
     * 初始化路由框架
     */
    private static void initRouter(Application application) {
        // 这两行必须写在init之前,否则这些配置在init过程中将无效
        if (MyApp.isDebug()) {
            XRouter.openLog();     // 打印日志
            XRouter.openDebug();   // 开启调试模式(如果在InstantRun模式下运行,必须开启调试模式!线上版本需要关闭,否则有安全风险)
        }
        XRouter.init(application);
    }

}

二、Viewbinding视图绑定

View Binding是Android Studio 3.6推出的新特性,目的是为了替代findViewById(内部实现还是使用findViewById)。。在启动视图绑定后,系统会为改模块中的每个xml文件生成一个绑定类,绑定类的实例包含对在相应布局中具有 ID 的所有视图的直接引用。
通过 Viewbinding 创建 Activity 的基类

public class BaseActivity<Binding extends ViewBinding> extends XPageActivity {
    /**
     * 是否支持侧滑返回
     */
    public static final String KEY_SUPPORT_SLIDE_BACK = "key_support_slide_back";
    /**
     * ViewBinding
     */
    protected Binding binding;

    @Override
    protected void attachBaseContext(Context newBase) {
        //注入字体
        super.attachBaseContext(ViewPumpContextWrapper.wrap(newBase));
    }

    @Override
    protected View getCustomRootView() {
        binding = viewBindingInflate(getLayoutInflater());
        return binding != null ? binding.getRoot() : null;
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        initStatusBarStyle();
        super.onCreate(savedInstanceState);
        registerSlideBack();
    }

    /**
     * 构建ViewBinding
     *
     * @param inflater  inflater
     * @return ViewBinding
     */
    @Nullable
    protected Binding viewBindingInflate(LayoutInflater inflater) {
        return null;
    }

    /**
     * 获取Binding
     *
     * @return Binding
     */
    public Binding getBinding() {
        return binding;
    }

    /**
     * 初始化状态栏的样式
     */
    protected void initStatusBarStyle() {

    }

    /**
     * 打开fragment
     *
     * @param clazz          页面类
     * @param addToBackStack 是否添加到栈中
     * @return 打开的fragment对象
     */
    public <T extends XPageFragment> T openPage(Class<T> clazz, boolean addToBackStack) {
        CoreSwitchBean page = new CoreSwitchBean(clazz)
                .setAddToBackStack(addToBackStack);
        return (T) openPage(page);
    }

    /**
     * 打开fragment
     *
     * @return 打开的fragment对象
     */
    public <T extends XPageFragment> T openNewPage(Class<T> clazz) {
        CoreSwitchBean page = new CoreSwitchBean(clazz)
                .setNewActivity(true);
        return (T) openPage(page);
    }

    public <T extends XPageFragment> T openNewPage(Class<T> clazz, Bundle bundle) {
        CoreSwitchBean page = new CoreSwitchBean(clazz, bundle)
                .setNewActivity(true);
        return (T) openPage(page);
    }

    /**
     * 切换fragment
     *
     * @param clazz 页面类
     * @return 打开的fragment对象
     */
    public <T extends XPageFragment> T switchPage(Class<T> clazz) {
        return openPage(clazz, false);
    }

    /**
     * 序列化对象
     *
     * @param object
     * @return
     */
    public String serializeObject(Object object) {
        return XRouter.getInstance().navigation(SerializationService.class).object2Json(object);
    }

    @Override
    protected void onRelease() {
        unregisterSlideBack();
        super.onRelease();
    }

    /**
     * 注册侧滑回调
     */
    protected void registerSlideBack() {
        if (isSupportSlideBack()) {
            SlideBack.with(this)
                    .haveScroll(true)
                    .edgeMode(ResUtils.isRtl() ? SlideBack.EDGE_RIGHT : SlideBack.EDGE_LEFT)
                    .callBack(this::popPage)
                    .register();
        }
    }

    /**
     * 注销侧滑回调
     */
    protected void unregisterSlideBack() {
        if (isSupportSlideBack()) {
            SlideBack.unregister(this);
        }
    }

    /**
     * @return 是否支持侧滑返回
     */
    protected boolean isSupportSlideBack() {
        CoreSwitchBean page = getIntent().getParcelableExtra(CoreSwitchBean.KEY_SWITCH_BEAN);
        return page == null || page.getBundle() == null || page.getBundle().getBoolean(KEY_SUPPORT_SLIDE_BACK, true);
    }

}

通过 Viewbinding 创建 Fragment 的基类(使用XPage框架搭建)

public abstract class BaseFragment<Binding extends ViewBinding> extends XPageFragment {

    private IProgressLoader mIProgressLoader;

    /**
     * ViewBinding
     */
    protected Binding binding;

    @Override
    protected View inflateView(LayoutInflater inflater, ViewGroup container) {
        binding = viewBindingInflate(inflater, container);
        return binding.getRoot();
    }

    /**
     * 构建ViewBinding
     *
     * @param inflater  inflater
     * @param container 容器
     * @return ViewBinding
     */
    @NonNull
    protected abstract Binding viewBindingInflate(LayoutInflater inflater, ViewGroup container);

    /**
     * 获取Binding
     *
     * @return Binding
     */
    public Binding getBinding() {
        return binding;
    }

    @Override
    protected void initPage() {
        initTitle();
        initViews();
        initListeners();
    }

    protected TitleBar initTitle() {
        return TitleUtils.addTitleBarDynamic((ViewGroup) getRootView(), getPageTitle(), v -> popToBack());
    }

    @Override
    protected void initListeners() {

    }

    /**
     * 获取进度条加载者
     *
     * @return 进度条加载者
     */
    public IProgressLoader getProgressLoader() {
        if (mIProgressLoader == null) {
            mIProgressLoader = ProgressLoader.create(getContext());
        }
        return mIProgressLoader;
    }

    /**
     * 获取进度条加载者
     *
     * @param message 提示信息
     * @return 进度条加载者
     */
    public IProgressLoader getProgressLoader(String message) {
        if (mIProgressLoader == null) {
            mIProgressLoader = ProgressLoader.create(getContext(), message);
        } else {
            mIProgressLoader.updateMessage(message);
        }
        return mIProgressLoader;
    }

    @Override
    public void onConfigurationChanged(@NonNull Configuration newConfig) {
        //屏幕旋转时刷新一下title
        super.onConfigurationChanged(newConfig);
        ViewGroup root = (ViewGroup) getRootView();
        if (root.getChildAt(0) instanceof TitleBar) {
            root.removeViewAt(0);
            initTitle();
        }
    }

    @Override
    public void onDestroyView() {
        if (mIProgressLoader != null) {
            mIProgressLoader.dismissLoading();
        }
        super.onDestroyView();
        binding = null;
    }

    @Override
    public void onResume() {
        super.onResume();
        MobclickAgent.onPageStart(getPageName());
    }

    @Override
    public void onPause() {
        super.onPause();
        MobclickAgent.onPageEnd(getPageName());
    }

    //==============================页面跳转api===================================//

    /**
     * 打开一个新的页面【建议只在主tab页使用】
     *
     * @param clazz 页面的类
     * @param <T>
     * @return
     */
    public <T extends XPageFragment> Fragment openNewPage(Class<T> clazz) {
        return new PageOption(clazz)
                .setNewActivity(true)
                .open(this);
    }

    /**
     * 打开一个新的页面【建议只在主tab页使用】
     *
     * @param pageName 页面名
     * @param <T>
     * @return
     */
    public <T extends XPageFragment> Fragment openNewPage(String pageName) {
        return new PageOption(pageName)
                .setAnim(CoreAnim.slide)
                .setNewActivity(true)
                .open(this);
    }


    /**
     * 打开一个新的页面【建议只在主tab页使用】
     *
     * @param clazz                页面的类
     * @param containActivityClazz 页面容器
     * @param <T>
     * @return
     */
    public <T extends XPageFragment> Fragment openNewPage(Class<T> clazz, @NonNull Class<? extends XPageActivity> containActivityClazz) {
        return new PageOption(clazz)
                .setNewActivity(true)
                .setContainActivityClazz(containActivityClazz)
                .open(this);
    }

    /**
     * 打开一个新的页面【建议只在主tab页使用】
     *
     * @param clazz 页面的类
     * @param key   入参的键
     * @param value 入参的值
     * @param <T>
     * @return
     */
    public <T extends XPageFragment> Fragment openNewPage(Class<T> clazz, String key, Object value) {
        PageOption option = new PageOption(clazz).setNewActivity(true);
        return openPage(option, key, value);
    }

    public Fragment openPage(PageOption option, String key, Object value) {
        if (value instanceof Integer) {
            option.putInt(key, (Integer) value);
        } else if (value instanceof Float) {
            option.putFloat(key, (Float) value);
        } else if (value instanceof String) {
            option.putString(key, (String) value);
        } else if (value instanceof Boolean) {
            option.putBoolean(key, (Boolean) value);
        } else if (value instanceof Long) {
            option.putLong(key, (Long) value);
        } else if (value instanceof Double) {
            option.putDouble(key, (Double) value);
        } else if (value instanceof Parcelable) {
            option.putParcelable(key, (Parcelable) value);
        } else if (value instanceof Serializable) {
            option.putSerializable(key, (Serializable) value);
        } else {
            option.putString(key, serializeObject(value));
        }
        return option.open(this);
    }

    /**
     * 打开页面
     *
     * @param clazz          页面的类
     * @param addToBackStack 是否加入回退栈
     * @param key            入参的键
     * @param value          入参的值
     * @param <T>
     * @return
     */
    public <T extends XPageFragment> Fragment openPage(Class<T> clazz, boolean addToBackStack, String key, String value) {
        return new PageOption(clazz)
                .setAddToBackStack(addToBackStack)
                .putString(key, value)
                .open(this);
    }

    /**
     * 打开页面
     *
     * @param clazz 页面的类
     * @param key   入参的键
     * @param value 入参的值
     * @param <T>
     * @return
     */
    public <T extends XPageFragment> Fragment openPage(Class<T> clazz, String key, Object value) {
        return openPage(clazz, true, key, value);
    }

    /**
     * 打开页面
     *
     * @param clazz          页面的类
     * @param addToBackStack 是否加入回退栈
     * @param key            入参的键
     * @param value          入参的值
     * @param <T>
     * @return
     */
    public <T extends XPageFragment> Fragment openPage(Class<T> clazz, boolean addToBackStack, String key, Object value) {
        PageOption option = new PageOption(clazz).setAddToBackStack(addToBackStack);
        return openPage(option, key, value);
    }

    /**
     * 打开页面
     *
     * @param clazz 页面的类
     * @param key   入参的键
     * @param value 入参的值
     * @param <T>
     * @return
     */
    public <T extends XPageFragment> Fragment openPage(Class<T> clazz, String key, String value) {
        return new PageOption(clazz)
                .putString(key, value)
                .open(this);
    }

    /**
     * 打开页面,需要结果返回
     *
     * @param clazz       页面的类
     * @param key         入参的键
     * @param value       入参的值
     * @param requestCode 请求码
     * @param <T>
     * @return
     */
    public <T extends XPageFragment> Fragment openPageForResult(Class<T> clazz, String key, Object value, int requestCode) {
        PageOption option = new PageOption(clazz).setRequestCode(requestCode);
        return openPage(option, key, value);
    }

    /**
     * 打开页面,需要结果返回
     *
     * @param clazz       页面的类
     * @param key         入参的键
     * @param value       入参的值
     * @param requestCode 请求码
     * @param <T>
     * @return
     */
    public <T extends XPageFragment> Fragment openPageForResult(Class<T> clazz, String key, String value, int requestCode) {
        return new PageOption(clazz)
                .setRequestCode(requestCode)
                .putString(key, value)
                .open(this);
    }

    /**
     * 打开页面,需要结果返回
     *
     * @param clazz       页面的类
     * @param requestCode 请求码
     * @param <T>
     * @return
     */
    public <T extends XPageFragment> Fragment openPageForResult(Class<T> clazz, int requestCode) {
        return new PageOption(clazz)
                .setRequestCode(requestCode)
                .open(this);
    }

    /**
     * 序列化对象
     *
     * @param object 需要序列化的对象
     * @return 序列化结果
     */
    public String serializeObject(Object object) {
        return XRouter.getInstance().navigation(SerializationService.class).object2Json(object);
    }

    /**
     * 反序列化对象
     *
     * @param input 反序列化的内容
     * @param clazz 类型
     * @return 反序列化结果
     */
    public <T> T deserializeObject(String input, Type clazz) {
        return XRouter.getInstance().navigation(SerializationService.class).parseObject(input, clazz);
    }


    @Override
    protected void hideCurrentPageSoftInput() {
        if (getActivity() == null) {
            return;
        }
        // 记住,要在xml的父布局加上android:focusable="true" 和 android:focusableInTouchMode="true"
        Utils.hideSoftInputClearFocus(getActivity().getCurrentFocus());
    }

}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值