Android学习笔记

Android学习笔记

Android学习笔记

配置

屏幕属性设置

屏幕属性设置需要在onCreate方法中地setContext()方法调用前调用。

全屏显示

requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
					 WindowManager.LayoutParams.FLAG_FULLSCREEN);

注意MainActivity要继承Activity,如果继承AppCompatActivity无效。

或者在style中进行配置,如此可不用添加代码,同时也可以继承自AppCompatActivity类

<resources xmlns:tools="http://schemas.android.com/tools">
    <style name="NoTitleFullScreen" parent="Theme.AppCompat.NoActionBar">
        <item name="android:windowFullscreen">true</item>
        <item name="windowNoTitle">true</item>
        <item name="windowActionBar">false</item>
    </style>
</resources>

保持屏幕唤醒

getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON,
                WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
//需要在AndroidManifest.xml中添加权限属性
//<uses-permission android:name="android.permission.WAKE_LOCK"/>

横屏设置

  • 配置位置:在AndroidManifest.xml文件中的activity标签内
  • 配置参数:android:screenOrientation="sensorLandscape"

相机获取图像

CameraManager类

相机设备管理类

名称功能
getCameraIdList()获取相机Id列表
openCamera()打开相机

报错

Access denied finding property "camera.hal1.packagelist"

在Manifest.xml文件中,加入外部存储写入权限。

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

PC传图像到Android

采用Android原生socket接收pc端python发送的图片数据

graph
AA[开始] --> A[PC获取图像长, 宽, 高组合成字符串, 用逗号分隔]
AA --> BB[Android等待接收尺寸数据]
A --> B[PC转化为byte数组]
    B --> C[PC发送尺寸信息]
    C --> CD[PC等待Android的接收完毕响应信息]
    C --> D[Android接收到尺寸信息]
    BB --> D
    D --> E[Android解析尺寸信息]
    E --> F[Android发送接收完毕响应信息]
    F --> G[PC端接收到响应信息]
    CD --> G
    F --> H[Android等待接收图片数据]
    G --> I[PC发送图片数据]
    I --> II[PC等待Android的接收完毕响应信息]
    I --> J[Android接收到图片数据]
    H --> J
    J --> K[Android发送接收完毕响应信息]
    K --> L[Android解析图片数据]
    L --> M[Android创建bitmap]
    M --> N[Android触发显示控件刷新]
    K --> NN[PC端接收到响应信息]
    II --> NN
    N --> O[结束]
    NN --> O

自定义控件

一、定义控件属性

在values文件夹下,在属性配置文件attr.xml中添加控件属性,如文件不存在,则可以创建一个。

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="SliderBar">
        <attr name="BarLength" format="integer"/>  <!-- 定义滑杆长度 -->
        <attr name="BarWidth" format="integer"/>  <!-- 定义滑杆宽度 -->
    </declare-styleable>
</resources>

二、创建控件类

该类可以继承不同类型Android原生控件,在本例中,创建了SliderBar控件类,继承View。

public class SliderBar extends View{}

三、定义一些全局变量

	private int barLength;
    private int barWidth;
    private float barRadius;
    private Paint barBackgroundPaint;
    private Paint blockPaint;

    private Point blockPosition;
    private Point mCenterPoint;

	private SliderBar.OnShakeListener mOnShakeListener;

四、创建构造函数

构造函数中实现对画笔,控件位置等全局变量和attr.xml中定义的属性的初始化。

public SliderBar(Context context, AttributeSet attrs) {
    super(context, attrs);
    initAttribute(context, attrs);

    if (isInEditMode()) {
    }

    // 移动区域画笔
    barBackgroundPaint = new Paint();
    barBackgroundPaint.setAntiAlias(true);

    // 滑块画笔
    blockPaint = new Paint();
    blockPaint.setAntiAlias(true);

    // 中心点
    mCenterPoint = new Point();
    // 滑块位置
    blockPosition = new Point();


}

private void initAttribute(Context context, AttributeSet attrs) {
    TypedArray typedArray = context.obtainStyledAttributes(attrs, 											R.styleable.SliderBar);
    // 摇杆半径
    barLength = typedArray.getInteger(R.styleable.SliderBar_BarLength, 420);
    barWidth = typedArray.getInteger(R.styleable.SliderBar_BarWidth, 40);
    //距离级别
    typedArray.recycle();
}

五、重构onMeasure函数

onMeasure函数定义了控件的大小。

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int measureWidth, measureHeight;

    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);

    if (widthMode == MeasureSpec.EXACTLY) {
        // 具体的值和match_parent
        measureWidth = widthSize;
    } else {
        // wrap_content
        measureWidth = barLength+barWidth;
    }
    if (heightMode == MeasureSpec.EXACTLY) {
        measureHeight = heightSize;
    } else {
        measureHeight = barWidth*2+15;
    }
    setMeasuredDimension(measureWidth, measureHeight);
}

六、重构onDraw函数

onDraw函数为控件绘制函数,用于绘制控件形状等。

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    int measuredWidth = getMeasuredWidth();
    int measuredHeight = getMeasuredHeight();

    int cx = measuredWidth / 2;
    int cy = measuredHeight / 2;

    // 中心点
    mCenterPoint.set(cx, cy);
    // 可移动区域的半径
    barRadius = (barLength - barWidth) / 2;
    // 摇杆位置
    if (0 == blockPosition.x || 0 == blockPosition.y) {
        blockPosition.set(mCenterPoint.x, mCenterPoint.y);
    }

    // 画可移动区域
    Rect rect = new Rect();
    rect.left = (int) (mCenterPoint.x - barRadius);
    rect.top = mCenterPoint.y - barWidth / 2;
    rect.right = (int) (mCenterPoint.x + barRadius);
    rect.bottom = mCenterPoint.y + barWidth / 2;
    barBackgroundPaint.setColor(Color.GRAY);
    canvas.drawRect(rect, barBackgroundPaint);
    canvas.drawCircle(rect.left, mCenterPoint.y, barWidth/2, 											barBackgroundPaint);
    canvas.drawCircle(rect.right, mCenterPoint.y, barWidth/2, 											barBackgroundPaint);
    blockPaint.setColor(Color.RED);
    canvas.drawCircle(blockPosition.x, mCenterPoint.y, barWidth, blockPaint);

}

七、重构onTouchEvent函数

onTouchEvent函数处理控件触控事件的响应。

@Override
public boolean onTouchEvent(MotionEvent event) {
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN: // 按下
            // 回调 开始
            callBackStart();
        case MotionEvent.ACTION_MOVE: // 移动
            float moveX = event.getX();
            float moveY = event.getY();
            blockPosition = getBlockPositionPoint(mCenterPoint, new Point((int) moveX, (int) moveY), barRadius);
            moveRocker(blockPosition.x, blockPosition.y);
            break;
        case MotionEvent.ACTION_UP: // 抬起
        case MotionEvent.ACTION_CANCEL: // 移出区域
            // 回调 结束
            callBackFinish();
            if (mOnShakeListener != null) {
                mOnShakeListener.getData(0.0);
            }
            moveRocker(mCenterPoint.x, mCenterPoint.y);
            break;
    }
    return true;
}

private Point getBlockPositionPoint(Point centerPoint, Point touchPoint, float radius){
    float dis = touchPoint.x - centerPoint.x;
    int showPointX = touchPoint.x;
    float abs_dis = Math.abs(dis);
    if (abs_dis > radius){
        showPointX = (int) (centerPoint.x + (dis / abs_dis) * radius);
    }
    int showPointY = centerPoint.y;
    callBack(showPointX - centerPoint.x);
    return new Point(showPointX, showPointY);
}

private void moveRocker(int x, int y) {
    blockPosition.set(x, y);
    invalidate();
}

private void callBackStart() {
    if (null != mOnShakeListener) {
        mOnShakeListener.onStart();
    }
}

private void callBack(double x) {
    if (null != mOnShakeListener) {

        mOnShakeListener.getData(x / barRadius);
    }
}

private void callBackFinish() {
    if (null != mOnShakeListener) {
        mOnShakeListener.onFinish();
    }
}

八、给出监听事件配置函数

public void setOnShakeListener(SliderBar.OnShakeListener listener) {
    mOnShakeListener = listener;
}

九、创建回调函数接口

public interface OnShakeListener {
    // 开始
    void onStart();

    /**
     * 摇动方向
     *
     * @param x 坐标
     */
    void getData(double x);

    // 结束
    void onFinish();
}

Android JNI配置Opencv

一、编译Opencv和Opencv contrib源码

step 1. 下载源码

opencv源码: https://github.com/opencv/opencv/releases/tag/4.5.4
opencv_contrib源码: https://github.com/opencv/opencv_contrib/releases/tag/4.5.4

源码下载完成后,两分源码解压到同一个目录下,目录结构如下:

# root
# |- opencv
# └- opencv-contrib

step 2. 编译源码

在根目录下,运行如下指令。

python ./opencv/platforms/android/build_sdk.py \
--extra_modules_path=path_to_opencv_contrib/modules/ \
--config ./opencv/platforms/android/ndk-22.config.py \
--sdk_path=path_to_sdk

⚠️注意:此处运行需要python环境,需要先解决python的环境依赖问题。

step 3. 代码部署

编译完成后,会在根目录下生成OpenCV-android-sdk文件夹,我们需要的文件内容都在里边

将该文件夹复制到Android的jni目录下,配置CMakeLists.txt

配置内容如下:

# 引入头文件
include_directories(${CMAKE_SOURCE_DIR}/OpenCV-android-sdk/sdk/native/jni/include)

# 引入库文件
set(OpenCV_DIR ${CMAKE_SOURCE_DIR}/OpenCV-android-sdk/sdk/native/jni)
find_package(OpenCV REQUIRED)

# 链接opencv
target_link_libraries( # Specifies the target library.
                       preciselanding

                       # Links the target library to the log library
                       # included in the NDK.
                       ${log-lib}
                       ${OpenCV_LIBS}
        ${jnigraphics-lib})

项目框架

一、MVVM框架

LiveData+ViewModel+DataBinding架构

注意事项: 在Activity中创建的变量会随着屏幕旋转而重置。

LiveData

MutableLiveData<String> msg;

ViewModel

public class testViewModel extends ViewModel
{
	;
}

DataBinding

xml
<data>
</data>

在layout中格式化字符串

android:text='@{String.format("%f", vm.var)}'

Activity基本实现

// mainBinding由系统自动生成,类的名字由首字母大写驼峰形式的xml名字+Binding组成
mainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
// 由ViewModelProvider创建vm类
mainActivityViewModel = new ViewModelProvider((ViewModelStoreOwner) this, new ViewModelProvider.NewInstanceFactory()).get(MainActivityViewModel.class);
// 将databinding和vm类进行绑定,其中方法名称由set+xml中定义的变量名,以首字母小写的驼峰形式呈现
mainBinding.setTest(mainActivityViewModel);  // 变量test绑定ViewModel
mainBinding.setClick(new ClickClass());  // 变量click绑定ViewModel
mainBinding.setLifecycleOwner(this);  //

防止Activity中变量重置

    @Override
    protected void onSaveInstanceState(@NonNull Bundle outState) {
        outState.putInt("count",times);
        super.onSaveInstanceState(outState);
    }

    // 防止旋转数据丢失,在activity中的关键数据在重构后进行恢复
    @Override
    protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        times = savedInstanceState.getInt("count");
    }
LICENSED UNDER CC BY-NC-SA 4.0