目录
1.动画
在我们的APP中很多地方都会用到动画,像下图的点赞等等都用到了动画
Android 中一共有三种方式来播放动画:
1.帧动画,像一般的 gif 图我们就可以把它看作一个帧动画的过程。在Android中,帧动画的本质是把一组预先准备好的图片循环切换播放,造成一种动画效果。
但是其消耗内存高。
2.补间动画,实际上安卓的大部分动画效果以前都是用补间动画来完成的
3.属性动画,可以说是完全替代了补间动画
我们今天讲的是安卓中使用最多的动画,属性动画。
动画到底由哪些东西组成?
- 控件透明度发生变化,不透明->透明。
- 缩放动画,控件大小变化
- 位移动画,位置发生移动
- 旋转动画
任何复杂的动画都能由上述几种动画组合而成。
1.案例一–四种简单动画
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="17dp"
android:text="缩放动画"
app:layout_constraintStart_toStartOf="@+id/button"
app:layout_constraintTop_toBottomOf="@+id/button" />
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="60dp"
android:text="透明度动画"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/button3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="17dp"
android:layout_marginEnd="3dp"
android:layout_marginRight="3dp"
android:text="位移动画"
app:layout_constraintEnd_toEndOf="@+id/button2"
app:layout_constraintTop_toBottomOf="@+id/button2" />
<Button
android:id="@+id/button4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="17dp"
android:text="旋转动画"
app:layout_constraintStart_toStartOf="@+id/button3"
app:layout_constraintTop_toBottomOf="@+id/button3" />
<ImageView
android:id="@+id/imageView"
android:layout_width="150dp"
android:layout_height="150dp"
android:layout_marginStart="32dp"
android:layout_marginLeft="32dp"
android:layout_marginTop="35dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView"
app:srcCompat="@drawable/love" />
</androidx.constraintlayout.widget.ConstraintLayout>
MainActivity.java代码
里面实现了简单的动画
ObjectAnimator.ofFloat参数
1.对哪个对象进行动画操作 2.动画需要执行的属性 3.动画的值的范围
package com.hnucm.android_66;
import androidx.appcompat.app.AppCompatActivity;
import android.animation.ObjectAnimator;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageView;
public class MainActivity extends AppCompatActivity {
ImageView imageView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
imageView=findViewById(R.id.imageView);
findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 透明度动画
//
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(imageView,"alpha",1,0);
// 设置动画的时间
objectAnimator.setDuration(5000);
objectAnimator.start();
}
});
findViewById(R.id.button2).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 缩放动画
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(imageView,"scaleX",0.5f,2);
objectAnimator.setDuration(5000);
objectAnimator.start();
}
});
findViewById(R.id.button3).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 位移动画
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(imageView,"translationX",-200,200);
objectAnimator.setDuration(5000);
objectAnimator.start();
}
});
findViewById(R.id.button4).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 旋转动画
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(imageView,"rotation",0,720);
objectAnimator.setDuration(5000);
objectAnimator.start();
}
});
}
}
2.案例二–复杂动画
实现了透明度的变化和位移的动画
使用方法和上面的简单动画一致
// 组动画
ObjectAnimator objectAnimator1 = ObjectAnimator.ofFloat(imageView,"alpha",1,0);
ObjectAnimator objectAnimator2 = ObjectAnimator.ofFloat(imageView,"scaleX",0,2);
ObjectAnimator objectAnimator3 = ObjectAnimator.ofFloat(imageView,"scaleY",0,2);
AnimatorSet animatorSet = new AnimatorSet();//set中元素不重复
animatorSet.play(objectAnimator1)
.with(objectAnimator2)
.with(objectAnimator3);
animatorSet.setDuration(2000);
animatorSet.start();
2.开源控件
1.实现圆角+阴影效果(CardView)
<com.google.android.material.card.MaterialCardView
android:layout_width="0dp"
android:layout_height="300dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:cardCornerRadius="20dp"
app:cardElevation="10dp"
app:layout_constraintTop_toBottomOf="@+id/textView">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/imageView"
android:layout_width="150dp"
android:layout_height="150dp"
android:layout_marginStart="116dp"
android:layout_marginLeft="116dp"
android:layout_marginTop="36dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/fruit" />
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="28dp"
android:text="西瓜"
android:textSize="20dp"
app:layout_constraintEnd_toEndOf="@+id/imageView"
app:layout_constraintStart_toStartOf="@+id/imageView"
app:layout_constraintTop_toBottomOf="@+id/imageView" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
在CardView中,遵循的是帧布局的原则,没学帧布局的同学,在CardView中直接用一个constraintlayout布局或者自己会用的布局,将需要用的控件包在里面就好了。
app:cardCornerRadius="20dp"
app:cardElevation="10dp"
这两个属性一个是设置圆角,一个是设置阴影。
2.图片选择和相机拍照
1.前期准备(获取权限,导入依赖)
图片选择框架依赖
implementation 'io.github.lucksiege:pictureselector:v2.7.2'
增加文件读写的权限
第三个是照相机的权限
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />
还要加上这句话
android:requestLegacyExternalStorage="true"
不加可能在高版本的手机上能用,但在低版本上可能用不了
文件的读写权限和我们以前学习的网络权限等等有些区别,它属于危险权限
危险权限和普通权限
从 Android6.0 开始,Google调整了应用的权限申请方案。调整之后将权限分级,分成了普通权限和危险权限
- 普通权限的授权方式跟之前一样,只需要在 Manifest 文件中申明即可
- 危险权限不仅需要在 Manifest 文件中声明,还需要在程序中调用官方提供的Api主动申请
危险权限有如下九种:读取联系人;打电话;读取用户日历信息;相机权限;传感器,获取用户位置;读取用户SD卡(拍的照片视频都在这里);多媒体(录音);短信。
那么让用户同意权限的弹窗如何调出来呢,我们使用第三方开源控件
首先导入依赖 申请危险权限
implementation 'com.yanzhenjie:permission:2.0.3'
这个开源控件支持的SDK的最低版本是19,所以我们可以修改
获取权限的案例
AndPermission.with(this)
.runtime()
.permission(Permission.WRITE_EXTERNAL_STORAGE, Permission.CAMERA)
.onGranted(permissions -> {
Toast.makeText(MainActivity2.this, "授权成功!", Toast.LENGTH_SHORT).show();
})
.onDenied(permissions -> {
//用户拒绝->强制退出app
Toast.makeText(MainActivity2.this, "未获取权限不能使用本app!", Toast.LENGTH_SHORT).show();
System.exit(0);
})
.start();
如果用户不允许以上权限,用户拒绝,市面上大多数APP则会直接退出。
2.读取用户相册
建GlideEngine.java类
官方代码,直接使用
package com.hnucm.android_66;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.PointF;
import android.graphics.drawable.Drawable;
import android.view.View;
import android.widget.ImageView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.graphics.drawable.RoundedBitmapDrawable;
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
import com.bumptech.glide.Glide;
import com.bumptech.glide.request.target.BitmapImageViewTarget;
import com.bumptech.glide.request.target.ImageViewTarget;
import com.luck.picture.lib.engine.ImageEngine;
import com.luck.picture.lib.listener.OnImageCompleteCallback;
import com.luck.picture.lib.tools.MediaUtils;
import com.luck.picture.lib.widget.longimage.ImageSource;
import com.luck.picture.lib.widget.longimage.ImageViewState;
import com.luck.picture.lib.widget.longimage.SubsamplingScaleImageView;
public class GlideEngine implements ImageEngine {
/**
* 加载图片
*
* @param context
* @param url
* @param imageView
*/
@Override
public void loadImage(@NonNull Context context, @NonNull String url, @NonNull ImageView imageView) {
Glide.with(context)
.load(url)
.into(imageView);
}
/**
* 加载网络图片适配长图方案
* # 注意:此方法只有加载网络图片才会回调
*
* @param context
* @param url
* @param imageView
* @param longImageView
* @param callback 网络图片加载回调监听 {link after version 2.5.1 Please use the #OnImageCompleteCallback#}
*/
@Override
public void loadImage(@NonNull Context context, @NonNull String url,
@NonNull ImageView imageView,
SubsamplingScaleImageView longImageView, OnImageCompleteCallback callback) {
Glide.with(context)
.asBitmap()
.load(url)
.into(new ImageViewTarget<Bitmap>(imageView) {
@Override
public void onLoadStarted(@Nullable Drawable placeholder) {
super.onLoadStarted(placeholder);
if (callback != null) {
callback.onShowLoading();
}
}
@Override
public void onLoadFailed(@Nullable Drawable errorDrawable) {
super.onLoadFailed(errorDrawable);
if (callback != null) {
callback.onHideLoading();
}
}
@Override
protected void setResource(@Nullable Bitmap resource) {
if (callback != null) {
callback.onHideLoading();
}
if (resource != null) {
boolean eqLongImage = MediaUtils.isLongImg(resource.getWidth(),
resource.getHeight());
longImageView.setVisibility(eqLongImage ? View.VISIBLE : View.GONE);
imageView.setVisibility(eqLongImage ? View.GONE : View.VISIBLE);
if (eqLongImage) {
// 加载长图
longImageView.setQuickScaleEnabled(true);
longImageView.setZoomEnabled(true);
longImageView.setDoubleTapZoomDuration(100);
longImageView.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CENTER_CROP);
longImageView.setDoubleTapZoomDpi(SubsamplingScaleImageView.ZOOM_FOCUS_CENTER);
longImageView.setImage(ImageSource.bitmap(resource),
new ImageViewState(0, new PointF(0, 0), 0));
} else {
// 普通图片
imageView.setImageBitmap(resource);
}
}
}
});
}
/**
* 加载网络图片适配长图方案
* # 注意:此方法只有加载网络图片才会回调
*
* @param context
* @param url
* @param imageView
* @param longImageView
* @ 已废弃
*/
@Override
public void loadImage(@NonNull Context context, @NonNull String url,
@NonNull ImageView imageView,
SubsamplingScaleImageView longImageView) {
Glide.with(context)
.asBitmap()
.load(url)
.into(new ImageViewTarget<Bitmap>(imageView) {
@Override
protected void setResource(@Nullable Bitmap resource) {
if (resource != null) {
boolean eqLongImage = MediaUtils.isLongImg(resource.getWidth(),
resource.getHeight());
longImageView.setVisibility(eqLongImage ? View.VISIBLE : View.GONE);
imageView.setVisibility(eqLongImage ? View.GONE : View.VISIBLE);
if (eqLongImage) {
// 加载长图
longImageView.setQuickScaleEnabled(true);
longImageView.setZoomEnabled(true);
longImageView.setDoubleTapZoomDuration(100);
longImageView.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CENTER_CROP);
longImageView.setDoubleTapZoomDpi(SubsamplingScaleImageView.ZOOM_FOCUS_CENTER);
longImageView.setImage(ImageSource.bitmap(resource),
new ImageViewState(0, new PointF(0, 0), 0));
} else {
// 普通图片
imageView.setImageBitmap(resource);
}
}
}
});
}
/**
* 加载相册目录
*
* @param context 上下文
* @param url 图片路径
* @param imageView 承载图片ImageView
*/
@Override
public void loadFolderImage(@NonNull Context context, @NonNull String url, @NonNull ImageView imageView) {
Glide.with(context)
.asBitmap()
.load(url)
.override(180, 180)
.centerCrop()
.sizeMultiplier(0.5f)
.placeholder(R.drawable.picture_image_placeholder)
.into(new BitmapImageViewTarget(imageView) {
@Override
protected void setResource(Bitmap resource) {
RoundedBitmapDrawable circularBitmapDrawable =
RoundedBitmapDrawableFactory.
create(context.getResources(), resource);
circularBitmapDrawable.setCornerRadius(8);
imageView.setImageDrawable(circularBitmapDrawable);
}
});
}
/**
* 加载gif
*
* @param context 上下文
* @param url 图片路径
* @param imageView 承载图片ImageView
*/
@Override
public void loadAsGifImage(@NonNull Context context, @NonNull String url,
@NonNull ImageView imageView) {
Glide.with(context)
.asGif()
.load(url)
.into(imageView);
}
/**
* 加载图片列表图片
*
* @param context 上下文
* @param url 图片路径
* @param imageView 承载图片ImageView
*/
@Override
public void loadGridImage(@NonNull Context context, @NonNull String url, @NonNull ImageView imageView) {
Glide.with(context)
.load(url)
.override(200, 200)
.centerCrop()
.placeholder(R.drawable.picture_image_placeholder)
.into(imageView);
}
private GlideEngine() {
}
private static GlideEngine instance;
public static GlideEngine createGlideEngine() {
if (null == instance) {
synchronized (GlideEngine.class) {
if (null == instance) {
instance = new GlideEngine();
}
}
}
return instance;
}
}
导入加载网络图片的依赖
implementation 'com.github.bumptech.glide:glide:4.12.0'
MainActivity2.java中加上
点击事件读取相册
findViewById(R.id.button5).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
PictureSelector.create(MainActivity2.this)
.openGallery(PictureMimeType.ofAll())
.imageEngine(GlideEngine.createGlideEngine())
.forResult(new OnResultCallbackListener<LocalMedia>() {
@Override
public void onResult(List<LocalMedia> result) {
// onResult Callback
for (LocalMedia localMedia : result){
Log.i("MainActivity",localMedia.getPath());
}
}
@Override
public void onCancel() {
// onCancel Callback
}
});
}
});
我们点击相册中的图片通过回调函数可以拿到图片的本地路径(/storage/emulated/0/DCIM)
将获取到的图片显示到界面上面(读取到的流->图片,自己写是很麻烦的,我们使用Glide开源框架很简单就能完成)
这里我们只对所选择的第一张图片展示出来
Glide.with(MainActivity2.this).load(Uri.fromFile(new File(result.get(0).getRealPath()))).into(imageView);
显示多张图片在回调函数中通过列表控件RecyclerView循环
3.应用程序打包
将代码变成apk文件
在国内我们直接打包成apk文件,在国外发布到市场上面不是以apk的形式发布的。
第一次打包的时候需要生成一个钥匙,证明这个apk文件是你的,增加安全性。你有一个钥匙来证明。点击create
输入自己设置的两个密码,自己记住
对应用程序进行签名和加密
等待片刻…
这个路径下会生成apk文件
这个apk我们便可以安装在手机上了。但是如果需要很多人使用,一个一个的发送apk肯定是不行的,所以我们需要将app发布在应用市场上面。
那么如何将应用程序发布到市场上面?未完待续…