比赛训练笔记 - Android

屏幕旋转

检测屏幕方向:

int orientation = getResources().getConfiguration().orientation;
boolean landscape = (orientation == Configuration.ORIENTATION_LANDSCAPE);

强制Activity横屏

AndroidManifest里给Activity标签添加属性:

android:screenOrientation="landscape"

切换时自动重建

创建layout时在Qualifier列表中添加Orientation属性。调用R.layout.xxx时会自动根据屏幕方向选择对应的layout。

Orientation QualifierOrientation Qualifier

切换横竖屏时Activity会自动重建。

  • 保存数据:覆盖onSaveInstanceState(outState:Bundle),往Bundle里写入数据
  • 恢复数据:从onCreate(savedInstanceState:Bundle?)参数中读取数据
    info:可以把要保留的数据存在模型类中。
    模型类实现Serializable接口,模型类中的其他模型类也要实现

切换时不重建

AndroidManifest里给Activity标签添加属性:

android:configChanges="orientation|screenSize"

表明在方向和屏幕大小改变时不要重建Activity,而是调用onConfigChanged方法。

媒体相关

包含拍照、录像、选择文件的操作。

基本流程:启动对应的Intent并startActivityForResult,结果在onActivityResult中返回。

拍照

  • Intent类型:MediaStore.ACTION_IMAGE_CAPTURE
  • 获取结果:

    • 获取拍照缩略图对象:data.getExtras().get("data")
    • 强制转换为Bitmap类型。
// 启动
Intent intent=new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(intent, requestCode);

// 获取结果(在onActivityResult)
Bitmap bitmap = (Bitmap)data.getExtras().get("data");

选择文件:

  • Intent类型:Intent.ACTION_GET_CONTENT
  • 限制文件类型:intent.setType(mimeType)填入MIME类型

    • 图片:image/* image/png image/jpeg ...
    • 视频:video/* video/mp4 ...
  • 获取结果:

    • 获取URI:data.getData()
    • 获取输入流:getContentResolver().openInputStream(uri)
    • 转换为图片:BitmapFactory.decodeStream(inputStream)

选择图片

// 启动
Intent intent=new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("image/*");
startActivityForResult(intent, requestCode);

// 获取结果(在onActivityResult)
Uri uri=data.getData();
InputStream is = getContentResolver().openInputStream(uri);
Bitmap bitmap = BitmapFactory.decodeStream(is);

录像

  • Intent类型:MediaStore.ACTION_VIDEO_CAPTURE
  • 获取结果:

    • 获取URI:data.getData()
    • VideoView播放:videoView.setVideoURI(uri) videoView.start()
    • 获取输入流:getContentResolver().openInputStream(uri)
// 启动
Intent intent=new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
startActivityForResult(intent, requestCode);

// 获取结果(在onActivityResult)
Uri uri=data.getData();
videoView.setVideoURI(uri);
videoView.start();

拨号

  • Intent类型:Intent.ACTION_DIAL
  • 电话号码URI:Uri.Parse("tel: 电话号码")
  • 设置电话号码:intent.setData(uri)
Intent intent = new Intent(Intent.ACTION_DIAL);
intent.setData(Uri.parse("tel: 12345"));
startActivity(intent);

画板

Canvas是操作Bitmap的工具,Bitmap需要Paint(笔刷)来进行绘画。

  • 创建自定义View
  • 初始化代码
Canvas canvas;
Bitmap bitmap;
Paint paintLine;
Paint paintBitmap;

// 创建线条笔刷
paintLine = new Paint();
paintLine.setColor(Color.BLUE);
paintLine.setStrokeWidth(4);
// 创建位图笔刷
paintBitmap = new Paint();

// 初始化位图和画布
addOnLayoutChangeListener((...) -> {
    Bitmap old = bitmap;
    bitmap = Bitmap.createBitmap(
        right - left,
        bottom - top,
        Bitmap.Config.ARGB_8888
    );
    canvas.setBitmap(bitmap);
    
    // 将旧画布内容转移到新画布
    if(old != null){
        canvas.drawBitmap(old, 0, 0, paintBitmap);
        old.recycle();
    }
});
  • 触摸事件中绘制线条
float lastX=0;
float lastY=0;

//=== Override onTouchEvent
switch(event.getActionMasked()) {
    case MotionEvent.ACTION_DOWN:
        lastX = event.getX();
        lastY = event.getY();
        return true;
    
    case MotionEvent.ACTION_MOVE:
    case MotionEvent.ACTION_UP:
        float x = event.getX();
        float y = event.getY();
        canvas.drawLine(
            lastX, lastY,
            x, y,
            paintLine
        );
        lastX = x;
        lastY = y;
        invalidate();
        return true;
}
return false;
  • 绘制事件中绘制bitmap
// === Override onDraw
canvas.drawBitmap(bitmap, 0, 0, paintBitmap);
  • 清空bitmap
// BlendMode [CLEAR]
canvas.drawColor(0, PorterDuff.Mode.CLEAR);
invalidate();

ListView 点击事件失效

ListView 中某一行如果有按钮,那一行将无法使用 ListView 本身的 OnItemClick 事件。
原因是点击事件被按钮优先吃掉,到达不了 ListView,因此也无法触发 ListView 的 Item 点击事件。

解决方法:

  • 把点击事件做进适配器里,设置 View 的 OnClickListener。(推荐,RecyclerView 也已经移除了 OnItemClick 事件。)
  • (不推荐!hack 做法,可能随时被修复。)在适配器里给每个按钮设置setFocusable(false) setFocusableInTouchMode(false),能保留 ListView 点击动画。

Timer

使用 Timer.cancel() 取消 Timer 之后,这个 Timer 就无法再使用了。
如果要复用 Timer,就储存启动任务时创建的 TimerTask 对象,调用TimerTask.cancel()只取消这一个任务。
TimerTask 取消之后也无法再使用相同的对象了。启动新的任务时也要创建新的 TimerTask 对象。

检测是否联网

需要权限:

  • ACCESS_WIFI_STATE
  • ACCESS_NETWORK_STATE

首先获取ConnectivityManager:

ConnectivityManager cm;

cm = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);

检查是否连接上任何网络:

NetworkInfo info = cm.getActiveNetworkInfo();
boolean connected = info != null && info.isConnected();

ArrayAdapter相关坑

使用AutoCompleteTextView时,文本框内容改变之后,通过修改原始列表并使用notifyDataSetChanged更新数据,下拉框的自动补全项不会更新。

解决方法:ArrayAdapter中包含和List一样的操作方法(add、remove、clear等)。不要直接操作原始列表,只使用Adapter提供的方法来操作列表。(类似WinForm中的BindingList)

API

  • Json数组类型:JArray

配置请求体限制

Web.config文件中。

<system.web><httpRuntime>标签中添加属性:

  • maxRequestLength="1048576"(单位KiloByte)。
  • (可选)executionTimeout="3600"(单位秒),延长执行时间限制。

    <system.web>
      <httpRuntime targetFramework="4.7.2" maxRequestLength="1048576" executionTimeout="3600" />
    </system.web>

warning:需要验证:在IIS7之后添加如下配置?上面设置的和下面设置的大小需匹配。

<system.webServer><requestFiltering>标签中添加标签:

  • <requestLimits maxAllowedContentLength="1073741824">(单位Byte)

    <system.webServer>
      <security>
          <requestFiltering>
              <requestLimits maxAllowedContentLength="1073741824" />
          </requestFiltering>
      </security>
    </system.webServer>

请求体直接传输二进制

Android端设置好Content-Type,直接把二进制流完整写入outputStream。

API端从Request中提取Content,读取出byte[]。
因为使用到异步方法,所以需要设置方法为async,返回值为Task。await异步方法。

var body = Request.Content;
var bodyBytes = await body.ReadAsByteArrayAsync();

映射服务器路径

var path = HttpContext.Current.Server.MapPath($"~/path");

目录符号:

  • ~:应用程序根目录
  • .:当前页面根目录
  • ..:当前页面的父目录

报错

1

global.asax 报错 Could not load type [Application类型]

是因为项目没有成功编译导致的。

2

The route template separator character '/' cannot appear consecutively. It must be separated by either a parameter or a literal value.

是因为某个函数的路由中包含了连续的(consecutively)两个/。例如某个[Route]路径以/开头了。例如:

[Route("/requested-by/{uid:int}")]

改成

[Route("requested-by/{uid:int}")]

3

The inline constraint resolver of type 'DefaultInlineConstraintResolver' was unable to resolve the following inline constraint: 'uid'.

是因为某个Route中写的路径参数有问题。例如:

[Route("requested-by/{int:uid}")]

改成

[Route("requested-by/{uid:int}")]

Route路劲参数的格式是名称:类型,如果类型是string,就不需要写:类型的部分,只需要写名称。

评论已关闭