Android App开发实战项目之大头贴App功能实现(附源码和演示 简单易上手)

简介: Android App开发实战项目之大头贴App功能实现(附源码和演示 简单易上手)

需要图片集和源码请点赞关注收藏后评论区留言~~~

一、需求描述

大头贴App有两个特征,第一个是头要大,拿来一张照片后把人像区域裁剪出来,这样新图片里的人头才会比较大,第二个是在周围贴上装饰物,而且装饰物还能随时更换,这样一副人像可以变换出许多张大头贴(类似于现在拍照APP的环境变换功能)

二、功能分析

 就人像剪裁功能而言,首先适当变换图片,把人像区域调整到屏幕中央,以便后续的裁剪操作,这些图片变换操作包括平移图片,旋转图片,水平反转图片等等。调整好人像位置以后,再来裁剪指定的人像区域,为了方便观察待裁剪区域和其余的图片区域,可将待裁剪区域高亮显示,同时暗色显示其余的图片区域。

 就人像装饰功能而言,可用于装饰的物品包括一段文字,图片标志,相框背景等等。待装饰的人像图片由前一步的人像裁剪而来,装饰完成的人像图片需要支持保存到存储卡,从而实现大头贴的多次加工操作

下面简单介绍一下源码之间的关系 便于理解

1:PortraitActivity  采集头像的活动页面 从原始图片裁剪指定的人像区域

2:PurikuraActivity  制作大头贴的活动页面,给采集来的头像添加各种装饰

采集头像的时候,先把裁剪后的头像保存为图片文件,再将图片文件路径返回给制作页面

三、效果展示

采集头像页面如下 可以选择左右反转上下移动等操作选择图片的不同位置

装饰图像效果如下

可以加上不同的背景和装饰文字

四、代码

采集图像类代码如下

package com.example.picture;
import androidx.appcompat.app.AppCompatActivity;
import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.util.Log;
import android.view.View;
import android.widget.CheckBox;
import android.widget.LinearLayout;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;
import com.example.picture.util.BitmapUtil;
import com.example.picture.util.DateUtil;
import com.example.picture.widget.BitmapView;
import com.example.picture.widget.CropImageView;
public class PortraitActivity extends AppCompatActivity implements SeekBar.OnSeekBarChangeListener {
    private final static String TAG = "PortraitActivity";
    private int COMBINE_CODE = 4; // 既可拍照获得现场图片、也可在相册挑选已有图片的请求码
    private BitmapView bv_photo; // 声明一个位图视图对象
    private CropImageView civ_photo; // 声明一个裁剪视图对象
    private LinearLayout ll_adjust; // 声明一个线性布局对象
    private SeekBar sb_scale; // 声明一个拖动条对象
    private SeekBar sb_horizontal; // 声明一个拖动条对象
    private SeekBar sb_vertical; // 声明一个拖动条对象
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_portrait);
        TextView tv_title = findViewById(R.id.tv_title);
        tv_title.setText("采集头像");
        TextView tv_option = findViewById(R.id.tv_option);
        tv_option.setText("确定");
        tv_option.setOnClickListener(v -> finishCollect());
        bv_photo = findViewById(R.id.bv_photo);
        civ_photo = findViewById(R.id.civ_photo);
        ll_adjust = findViewById(R.id.ll_adjust);
        findViewById(R.id.btn_combine).setOnClickListener(v -> openSelectDialog());
        CheckBox ck_flip = findViewById(R.id.ck_flip);
        ck_flip.setOnCheckedChangeListener((buttonView, isChecked) -> {
            bv_photo.flip(); // 左右翻转图像
            civ_photo.flip(); // 左右翻转图像
        });
        sb_scale = findViewById(R.id.sb_scale);
        sb_horizontal = findViewById(R.id.sb_horizontal);
        sb_vertical = findViewById(R.id.sb_vertical);
        sb_scale.setOnSeekBarChangeListener(this);
        sb_horizontal.setOnSeekBarChangeListener(this);
        sb_vertical.setOnSeekBarChangeListener(this);
    }
    // 结束头像采集
    private void finishCollect() {
        if (civ_photo.getCropBitmap() == null) {
            Toast.makeText(this, "请先选好头像图片", Toast.LENGTH_SHORT).show();
            return;
        }
        // 生成图片文件的保存路径
        String path = String.format("%s/%s.jpg",
                getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).toString(),
                DateUtil.getNowDateTime());
        BitmapUtil.saveImage(path, civ_photo.getCropBitmap()); // 把位图保存为图片文件
        BitmapUtil.notifyPhotoAlbum(this, path); // 通知相册来了张新图片
        Intent intent = new Intent(); // 创建一个新意图
        intent.putExtra("pic_path", path);
        setResult(Activity.RESULT_OK, intent); // 携带意图返回前一个页面
        finish(); // 关闭当前页面
    }
    // 打开选择对话框(要拍照还是去相册)
    private void openSelectDialog() {
        // 声明相机的拍照行为
        Intent photoIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        Intent[] intentArray = new Intent[] { photoIntent };
        // 声明相册的打开行为
        Intent albumIntent = new Intent(Intent.ACTION_GET_CONTENT);
        albumIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, false); // 是否允许多选
        albumIntent.setType("image/*"); // 类型为图像
        // 容纳相机和相册在内的选择意图
        Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER);
        chooserIntent.putExtra(Intent.EXTRA_TITLE, "请拍照或选择图片");
        chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentArray);
        chooserIntent.putExtra(Intent.EXTRA_INTENT, albumIntent);
        // 创建封装好标题的选择器意图
        Intent chooser = Intent.createChooser(chooserIntent, "选择图片");
        // 在页面底部弹出多种选择方式的列表对话框
        startActivityForResult(chooser, COMBINE_CODE);
    }
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
        super.onActivityResult(requestCode, resultCode, intent);
        if (resultCode == RESULT_OK && requestCode == COMBINE_CODE) { // 从组合选择返回
            if (intent.getData() != null) { // 从相册选择一张照片
                Uri uri = intent.getData(); // 获得已选择照片的路径对象
                // 根据指定图片的uri,获得自动缩小后的位图对象
                Bitmap bitmap = BitmapUtil.getAutoZoomImage(this, uri);
                showPicture(bitmap); // 显示已选择的图片
            } else if (intent.getExtras() != null) { // 拍照的缩略图
                Object obj = intent.getExtras().get("data");
                if (obj instanceof Bitmap) { // 属于位图类型
                    Bitmap bitmap = (Bitmap) obj; // 强制转成位图对象
                    showPicture(bitmap); // 显示已选择的图片
                }
            }
        }
    }
    // 显示已选择的图片
    private void showPicture(Bitmap origin) {
        bv_photo.setDrawingCacheEnabled(true); // 开启位图视图的绘图缓存
        bv_photo.setImageBitmap(origin); // 设置图像视图的位图对象
        ll_adjust.setVisibility(View.VISIBLE);
        sb_scale.setProgress(30); // 设置拖动条的当前进度
        sb_horizontal.setProgress(50); // 设置拖动条的当前进度
        sb_vertical.setProgress(50); // 设置拖动条的当前进度
        Bitmap bitmap = bv_photo.getDrawingCache(); // 从绘图缓存获取位图对象
        if (bitmap == null) {
            return;
        }
        int width = bitmap.getWidth(), height = bitmap.getHeight();
        civ_photo.setOrigBitmap(bitmap); // 设置裁剪视图的原始位图
        // 设置位图的矩形边界
        civ_photo.setBitmapRect(new Rect(width/8, height/8, width/4*3, height/4*3));
        bv_photo.setDrawingCacheEnabled(false); // 关闭位图视图的绘图缓存
    }
    // 刷新图像展示
    private void refreshImage() {
        Bitmap bitmap = bv_photo.getDrawingCache(); // 从绘图缓存获取位图对象
        civ_photo.setOrigBitmap(bitmap); // 设置裁剪视图的原始位图
        civ_photo.setBitmapRect(civ_photo.getBitmapRect()); // 设置裁剪视图的位图边界
    }
    @Override
    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
        bv_photo.setDrawingCacheEnabled(true); // 开启位图视图的绘图缓存
        if (seekBar.getId() == R.id.sb_scale) {
            bv_photo.setScaleRatio(progress/33f, true); // 设置位图视图的缩放比率
        } else if (seekBar.getId()==R.id.sb_horizontal || seekBar.getId()==R.id.sb_vertical) {
            int viewWidth = bv_photo.getMeasuredWidth(); // 获取视图的实际宽度
            int viewHeight = bv_photo.getMeasuredHeight(); // 获取视图的实际高度
            int offsetX = (int) ((sb_horizontal.getProgress()-50)/50f*viewWidth);
            int offsetY = (int) ((sb_vertical.getProgress()-50)/50f*viewHeight);
            Log.d(TAG, "viewWidth="+viewWidth+", offsetX="+offsetX+", viewHeight="+viewHeight+", offsetY="+offsetY);
            bv_photo.setOffset(offsetX, offsetY, true); // 设置位图视图的偏移距离
        }
        refreshImage(); // 刷新图像展示
        bv_photo.setDrawingCacheEnabled(false); // 关闭位图视图的绘图缓存
    }
    @Override
    public void onStartTrackingTouch(SeekBar seekBar) {}
    @Override
    public void onStopTrackingTouch(SeekBar seekBar) {}
}

装饰图像类代码如下

package com.example.picture;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Typeface;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
import com.example.picture.util.BitmapUtil;
import com.example.picture.util.DateUtil;
import com.example.picture.widget.DecorateImageView;
public class PurikuraActivity extends AppCompatActivity {
    private final static String TAG = "PurikuraActivity";
    private int COLLECT_CODE = 14; // 采集头像的请求码
    private DecorateImageView div_photo; // 声明一个装饰视图对象
    private EditText et_text; // 声明一个编辑框对象
    private boolean haveCollected = false; // 是否已采集头像
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_purikura);
        findViewById(R.id.iv_back).setOnClickListener(v -> finish());
        TextView tv_title = findViewById(R.id.tv_title);
        tv_title.setText("大头贴");
        TextView tv_option = findViewById(R.id.tv_option);
        tv_option.setText("保存");
        tv_option.setOnClickListener(v -> savePurikura());
        div_photo = findViewById(R.id.div_photo);
        et_text = findViewById(R.id.et_text);
        findViewById(R.id.btn_text).setOnClickListener(v -> {
            String text = et_text.getText().toString();
            div_photo.showText(text, false); // 显示装饰文本
        });
        findViewById(R.id.btn_collect).setOnClickListener(v -> {
            // 下面跳到头像采集页面
            Intent intent = new Intent(this, PortraitActivity.class);
            startActivityForResult(intent, COLLECT_CODE);
        });
        initLogoSpinner(); // 初始化标志图片下拉框
        initFrameSpinner(); // 初始化相框种类下拉框
    }
    // 保存加工好的大头贴图片
    private void savePurikura() {
        if (!haveCollected) {
            Toast.makeText(this, "请先采集头像图片", Toast.LENGTH_SHORT).show();
            return;
        }
        div_photo.setDrawingCacheEnabled(true); // 开启装饰视图的绘图缓存
        Bitmap bitmap = div_photo.getDrawingCache(); // 从绘图缓存获取位图对象
        // 生成图片文件的保存路径
        String path = String.format("%s/%s.jpg",
                getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).toString(),
                DateUtil.getNowDateTime());
        BitmapUtil.saveImage(path, bitmap); // 把位图保存为图片文件
        BitmapUtil.notifyPhotoAlbum(this, path); // 通知相册来了张新图片
        Toast.makeText(this, "已保存大头贴图片 "+path, Toast.LENGTH_SHORT).show();
    }
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
        super.onActivityResult(requestCode, resultCode, intent);
        if (resultCode == RESULT_OK && requestCode == COLLECT_CODE) { // 从采集头像返回
            String pic_path = intent.getStringExtra("pic_path");
            div_photo.setImageURI(Uri.parse(pic_path)); // 设置图像视图的路径对象
            haveCollected = true;
        }
    }
    // 初始化标志图片下拉框
    private void initLogoSpinner() {
        ArrayAdapter<String> logoAdapter = new ArrayAdapter<>(this,
                R.layout.item_select, logoNameArray);
        Spinner sp_logo = findViewById(R.id.sp_logo);
        sp_logo.setPrompt("请选择标志图片");
        sp_logo.setAdapter(logoAdapter);
        sp_logo.setOnItemSelectedListener(new LogoSelectedListener());
        sp_logo.setSelection(0);
    }
    private String[] logoNameArray = {"无标志", "春之兰花", "夏之荷花", "秋之菊花", "冬之梅花"};
    class LogoSelectedListener implements AdapterView.OnItemSelectedListener {
        public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
            Bitmap bitmap = null;
            if (arg2 == 1) { // 春之兰花
                bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.flower_orchid);
            } else if (arg2 == 2) { // 夏之荷花
                bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.flower_lotus);
            } else if (arg2 == 3) { // 秋之菊花
                bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.flower_chrysanthemum);
            } else if (arg2 == 4) { // 冬之梅花
                bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.flower_plum);
            }
            div_photo.showLogo(bitmap, false); // 显示装饰标志
        }
        public void onNothingSelected(AdapterView<?> arg0) {}
    }
    // 初始化相框种类下拉框
    private void initFrameSpinner() {
        ArrayAdapter<String> frameAdapter = new ArrayAdapter<>(this,
                R.layout.item_select, frameNameArray);
        Spinner sp_frame = findViewById(R.id.sp_frame);
        sp_frame.setPrompt("请选择相框种类");
        sp_frame.setAdapter(frameAdapter);
        sp_frame.setOnItemSelectedListener(new FrameSelectedListener());
        sp_frame.setSelection(0);
    }
    private String[] frameNameArray = {"无相框", "长方相框", "椭圆相框", "稻草相框", "爱心相框"};
    class FrameSelectedListener implements AdapterView.OnItemSelectedListener {
        public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
            Bitmap bitmap = null;
            if (arg2 == 1) { // 长方相框
                bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.photo_frame1);
            } else if (arg2 == 2) { // 椭圆相框
                bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.photo_frame2);
            } else if (arg2 == 3) { // 稻草相框
                bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.photo_frame3);
            } else if (arg2 == 4) { // 爱心相框
                bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.photo_frame4);
            }
            div_photo.showFrame(bitmap, false); // 显示装饰相框
        }
        public void onNothingSelected(AdapterView<?> arg0) {}
    }
}

XML文件

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <include layout="@layout/title_purikura" />
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="270dp" >
        <com.example.picture.widget.BitmapView
            android:id="@+id/bv_photo"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@color/white"
            android:scaleType="centerCrop" />
        <com.example.picture.widget.CropImageView
            android:id="@+id/civ_photo"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </RelativeLayout>
    <Button
        android:id="@+id/btn_combine"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="拍照或从相册选取"
        android:textColor="@color/black"
        android:textSize="17sp" />
    <LinearLayout
        android:id="@+id/ll_adjust"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:orientation="vertical"
        android:visibility="invisible">
        <CheckBox
            android:id="@+id/ck_flip"
            android:layout_width="match_parent"
            android:layout_height="40dp"
            android:text="是否左右翻转图像"
            android:textColor="@color/black"
            android:textSize="17sp" />
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="30dp"
            android:layout_margin="10dp"
            android:gravity="center"
            android:orientation="horizontal">
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="缩小"
                android:textColor="@color/black"
                android:textSize="17sp" />
            <SeekBar
                android:id="@+id/sb_scale"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:max="100"
                android:progress="30" />
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="放大"
                android:textColor="@color/black"
                android:textSize="17sp" />
        </LinearLayout>
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="30dp"
            android:layout_margin="10dp"
            android:gravity="center"
            android:orientation="horizontal">
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="左移"
                android:textColor="@color/black"
                android:textSize="17sp" />
            <SeekBar
                android:id="@+id/sb_horizontal"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:max="100"
                android:progress="50" />
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="右移"
                android:textColor="@color/black"
                android:textSize="17sp" />
        </LinearLayout>
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="30dp"
            android:layout_margin="10dp"
            android:gravity="center"
            android:orientation="horizontal">
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="上移"
                android:textColor="@color/black"
                android:textSize="17sp" />
            <SeekBar
                android:id="@+id/sb_vertical"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:max="100"
                android:progress="50" />
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="下移"
                android:textColor="@color/black"
                android:textSize="17sp" />
        </LinearLayout>
    </LinearLayout>
</LinearLayout>

创作不易 觉得有帮助请点赞关注收藏~~~

相关文章
|
2月前
|
Android开发 数据安全/隐私保护 开发者
Android自定义view之模仿登录界面文本输入框(华为云APP)
本文介绍了一款自定义输入框的实现,包含静态效果、hint值浮动动画及功能扩展。通过组合多个控件完成界面布局,使用TranslateAnimation与AlphaAnimation实现hint文字上下浮动效果,支持密码加密解密显示、去除键盘回车空格输入、光标定位等功能。代码基于Android平台,提供完整源码与attrs配置,方便复用与定制。希望对开发者有所帮助。
|
5月前
|
JavaScript 前端开发 Android开发
【03】仿站技术之python技术,看完学会再也不用去购买收费工具了-修改整体页面做好安卓下载发给客户-并且开始提交网站公安备案-作为APP下载落地页文娱产品一定要备案-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
【03】仿站技术之python技术,看完学会再也不用去购买收费工具了-修改整体页面做好安卓下载发给客户-并且开始提交网站公安备案-作为APP下载落地页文娱产品一定要备案-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
158 13
【03】仿站技术之python技术,看完学会再也不用去购买收费工具了-修改整体页面做好安卓下载发给客户-并且开始提交网站公安备案-作为APP下载落地页文娱产品一定要备案-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
|
1月前
|
开发工具 Android开发
ArkUI-X添加到现有Android项目中
本教程介绍如何使用ArkUI-X SDK开发Android AAR,实现ArkTS声明式开发在Android平台的显示。主要内容包括:1) 跨平台Library工程开发;2) AAR在Android应用中的集成方式。通过ACE Tools或DevEco Studio完成AAR构建,初始化ArkUI-X,并通过Activity或Fragment加载页面。具体步骤涵盖工程创建、构建AAR包、配置Manifest及使用Intent或Fragment打开页面等。
130 57
|
4月前
|
JSON 自然语言处理 前端开发
【01】对APP进行语言包功能开发-APP自动识别地区ip后分配对应的语言功能复杂吗?-成熟app项目语言包功能定制开发-前端以uniapp-基于vue.js后端以laravel基于php为例项目实战-优雅草卓伊凡
【01】对APP进行语言包功能开发-APP自动识别地区ip后分配对应的语言功能复杂吗?-成熟app项目语言包功能定制开发-前端以uniapp-基于vue.js后端以laravel基于php为例项目实战-优雅草卓伊凡
200 72
【01】对APP进行语言包功能开发-APP自动识别地区ip后分配对应的语言功能复杂吗?-成熟app项目语言包功能定制开发-前端以uniapp-基于vue.js后端以laravel基于php为例项目实战-优雅草卓伊凡
|
2月前
|
XML 搜索推荐 Android开发
Android改变进度条控件progressbar的样式(根据源码修改)
本文介绍了如何基于Android源码自定义ProgressBar样式。首先分析了系统源码中ProgressBar样式的定义,发现其依赖一张旋转图片实现动画效果。接着分两步指导开发者实现自定义:1) 模仿源码创建一个旋转动画XML文件(放置在drawable文件夹),修改图片为自定义样式;2) 在UI控件中通过`indeterminateDrawable`属性应用该动画。最终实现简单且个性化的ProgressBar效果,附带效果图展示。
|
3月前
|
数据采集 JSON 网络安全
移动端数据抓取:Android App的TLS流量解密方案
本文介绍了一种通过TLS流量解密技术抓取知乎App热榜数据的方法。利用Charles Proxy解密HTTPS流量,分析App与服务器通信内容;结合Python Requests库模拟请求,配置特定请求头以绕过反爬机制。同时使用代理IP隐藏真实IP地址,确保抓取稳定。最终成功提取热榜标题、内容简介、链接等信息,为分析热点话题和用户趋势提供数据支持。此方法也可应用于其他Android App的数据采集,但需注意选择可靠的代理服务。
131 11
移动端数据抓取:Android App的TLS流量解密方案
|
3月前
|
NoSQL 应用服务中间件 PHP
布谷一对一直播源码android版环境配置流程及功能明细
部署需基于 CentOS 7.9 系统,硬盘不低于 40G,使用宝塔面板安装环境,包括 PHP 7.3(含 Redis、Fileinfo 扩展)、Nginx、MySQL 5.6、Redis 和最新 Composer。Swoole 扩展需按步骤配置。2021.08.05 后部署需将站点目录设为 public 并用 ThinkPHP 伪静态。开发环境建议 Windows 操作系统与最新 Android Studio,基础配置涉及 APP 名称修改、接口域名更换、包名调整及第三方登录分享(如 QQ、微信)的配置,同时需完成阿里云与腾讯云相关设置。
|
5月前
|
缓存 Java 测试技术
【01】噩梦终结flutter配安卓android鸿蒙harmonyOS 以及next调试环境配鸿蒙和ios真机调试环境-flutter项目安卓环境配置-gradle-agp-ndkVersion模拟器运行真机测试环境-本地环境搭建-如何快速搭建android本地运行环境-优雅草卓伊凡-很多人在这步就被难倒了
【01】噩梦终结flutter配安卓android鸿蒙harmonyOS 以及next调试环境配鸿蒙和ios真机调试环境-flutter项目安卓环境配置-gradle-agp-ndkVersion模拟器运行真机测试环境-本地环境搭建-如何快速搭建android本地运行环境-优雅草卓伊凡-很多人在这步就被难倒了
415 3
【01】噩梦终结flutter配安卓android鸿蒙harmonyOS 以及next调试环境配鸿蒙和ios真机调试环境-flutter项目安卓环境配置-gradle-agp-ndkVersion模拟器运行真机测试环境-本地环境搭建-如何快速搭建android本地运行环境-优雅草卓伊凡-很多人在这步就被难倒了
|
Web App开发 关系型数据库
pc/app 项目/功能设计
2015-08-11 15:29:59 首先, 提供服务的整个系统包含哪几个设备 用户->[PC浏览器|APP|APP浏览器]->web服务器->[PHP/JAVA组件]->[MySQL/Redis]->[云服务/本地物理集群] 第一功能的使用目标, 是PC还是APP 第二信息交流格式, j...
895 0
|
3月前
|
人工智能 JSON 小程序
【一步步开发AI运动APP】七、自定义姿态动作识别检测——之规则配置检测
本文介绍了如何通过【一步步开发AI运动APP】系列博文,利用自定义姿态识别检测技术开发高性能的AI运动应用。核心内容包括:1) 自定义姿态识别检测,满足人像入镜、动作开始/停止等需求;2) Pose-Calc引擎详解,支持角度匹配、逻辑运算等多种人体分析规则;3) 姿态检测规则编写与执行方法;4) 完整示例展示左右手平举姿态检测。通过这些技术,开发者可轻松实现定制化运动分析功能。

热门文章

最新文章