比赛训练笔记 - 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。
切换横竖屏时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)
- 获取URI:
选择图片
// 启动
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)
- 获取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>
在
<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
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,就不需要写:类型
的部分,只需要写名称。
评论已关闭