In this article
Apt is used to realize page jump and shared animation. Shared animation can be set to enter animation time, exit animation time, enter animation interpolator, exit animation interpolator, enter animation priority and exit animation priority respectively. Multiple shared animations are executed in parallel at the same level and sequentially at different levels.
TODO
-
Activity jump parameter identification, shared element identification; -
Multiple shared elements perform the animation -
Jump animation can be customized implementation -
Add animation execution state callback -
Set animation string parallel logic
Results show
- The shared element attribute shown above
- The shared element attribute shown above
Knowledge to master
What is the apt
APT(Annotation Processing Tool) is a javAC Tool. It can scan and process annotations during compilation and obtain information about annotations and annotated objects. These information can be used to generate code that meets business requirements without manually writing some business codes. Improved development efficiency. All of these things are done by the compiler, apt improves the performance of the program compared to the traditional reflection method to do operations at run time.
Sharing the animation
Activity transitions in the Material Design application create visual connections between different states through animations and transitions between common elements. You can specify custom animations for entry and exit transitions, as well as transitions between activities that share elements.
Shared animation is a transition shared element animation for activities on Android5.0 and higher. You can define different views in two activities to use a common Android :transitionName, and there will be visual connections between page jumps. Android For Developers
Implementation steps
First let’s look at the project module:
- Annotations: A Java module that stores annotations
- Compiler: Our annotation processor module performs our annotation processing during compilation to generate Java or Kotlin code that meets business requirements.
- Runtime: Defines utility classes to handle Android animations and page jumps.
- App: Test module
Writing the Annotations module
Our first step is to write our annotations and define the information we need. In this case, we need information about the Activity, information about the Intent when jumping to the Activity, and information about the shared element.
When we write annotations we use two annotations, one is @target and the other is @Retention. Where @target indicates the Target of the annotation, and @retention indicates the Retention position of the annotation.
@target Parameter Meaning
- Elementtype. TYPE: used on classes or interfaces;
- Elementtype. FIELD: used on member variables of a class;
- ElementType.METHOD: used on methods;
- Elementtype. PARAMETER: used on method parameters;
- CONSTRUCTOR: used on constructors;
- Elementtype. LOCAL_VARIABLE: used on local variables;
- Elementtype. ANNOTATION_TYPE: used on annotations;
- Elementtype. PACKAGE: Used to record PACKAGE information for Java files;
- Elementtype. TYPE_PARAMETER: Type parameters used to annotate generics;
- Elementtype. TYPE_USE: annotates various types;
@retention Parameter Meaning
- Retentionpolicy. SOURCE: stays in the original file and does not generate class bytecode;
- Retentionpolicy. CLASS: remains in the CLASS file and is not available at runtime;
- Retentionpolicy. RUNTIME: reserved to RUNTIME, available at RUNTIME;
Build the Annotation
- We want the processor to only handle the Activity we marked, so define Builder.class:
- We define the parameter annotation that the jump must require:
- Define the jump optional parameter annotation(with default values):
- Annotation: Define shared element information annotation:
- Define the entry animation completion callback annotation:
Writing annotation handlers
Define BuilderProcessor
Builderprocessor. kt inherits AbstractProcessor implementation methods to handle custom interfaces.
-
Including getSupportedAnnotationTypes method is used to define the processor to deal with what kind of comments, you can also use the @ SupportedAnnotationTypes annotations to define.
-
The process method is used to collect information about annotations and their target for subsequent generation.
The logic we are doing here is:
- Collect all Activity class information annotated with Builder annotations into a Map.
- Collect the information annotated by @Required, @Optional, @Sharedelement, and @Runenteranim and add it to the Activity information marked by the corresponding Builder in the Map.
- Iterate through the Map and use the Activity information to generate the corresponding Builder class for auxiliary invocation.
The Entity class
ActivityClass
- Fields stores information about member variables (required parameters, optional parameters, and shared elements).
- StartMethodName Holds the name of the animation’s start callback method.
- EndMethodName holds the name of the end-of-animation callback method.
Field
This is where basic information about member variables is recorded and the Comparable interface is implemented to sort them.
OptionalField
OptionalField inherits from the Field class, rewrites the prefix name, reorders the sorting logic, and sorts only with OptionalField.
SharedElementField
SharedElementField records information for shared elements.
Write the Runtima module
This module defines some utility classes to handle Android animation related, page jump related
Define the View information class
In order to transmit the information of a View through an Intent, it needs to be serialized, so we implement the Parcelable interface. In the View information class, we record the information of the View in the previous activity and the ID of the View in the jump activity. There are also some arguments that need to be passed to execute the animation, so the ViewAttrs class looks like this:
State saving implementation
In order to achieve the state of preservation, we want to get the Application, then registerActivityLifecycleCallbacks, to monitor the Activity of the life cycle, save the current page in onActivitySaveInstanceState callback data, Reload the data in the onActivityCreated callback. The ActivityBuilder class defines the startActivity method to determine whether the context is an Activity. If it is not, add intent.flag_activitY_new_task to the Intent.
Shared animation priority implementation
The implementation of the shared animation priority execution logic is to sort all the shared elements through the PriorityQueue, and execute the animation according to the parallel and serial logic of different levels. Record the maximum time required for each level, find the maximum total time, delay sending animation to complete the callback.
Note: During the entry animation, there will be a problem with the View moving when executing the exit animation. This is because we used the property animation set by the view.animation () method. If we execute two animations on the same View at the same time, there will be a property change error. I added a volatile property to prevent this from happening to the entry animation.
Use Javapoet and KotlinPoet to generate code
Javapoet is a framework developed by The great wizard Of JakeWharton that makes it easy to generate Java source files without having to append() with StringBuilder. Kotlinpoet is the generated version of the Kotlin source file for Javapoet. In APT we use these two frameworks to generate relevant code.
ActivityClassBuilder
This class is used to generate activityNameBuilder.java classes tagged @buidler, where TypeSpec. ClassBuilder (name) creates Java classes, and addModifiers define class modifiers. Use the ConstantBuilder class to generate the member variables of the class, use startMethodBuilder to generate the start method, Use SaveStateMethodBuilder to generate the save state method, InjectMethodBuilder to generate the injection method, RunExitAnimMethodBuilder generates the exit animation method, and finally calls the Build () method to build the completion class, which is then written to the file.
InjectMethodBuilder
Here we will only use one method generation to illustrate how methods are generated.
- Methodspec.methodbuilder (name) to create a method named name;
- AddParameter (type, name) adds a parameter. Type is the type and name is the parameter name.
- AddModifiers are add method modifiers
- $defines a variable. If it’s followed by a T, it means the variable is a class. If it’s L, it means it’s putting itself in.
- BeginControlFlow conditional control statements need to be added using this method, don’t forget to call endControlFlow at the end of the conditional control line, and endControlFlow should be called for each beginControlFlow.
Test app module introduction use
- Add related modules to the build.gradle file of the APP test module
- We then use our annotations in the Activity class where we need to use shared elements
- In build projects, can be in/app/build/generated/source/kapt/debug (release) found in our automatically generated DetailsActivityBuilder
- Jump by calling the generated code in mainActivity
eggs
There is no user-aware injection framework
While working with LeakCanary, it turns out that the framework only needs to be introduced to work and I wonder how he did it. After reading the source code, we found that ContentProvider is used to achieve automatic injection. The specific methods are as follows:
- Register the provider in androidmanifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.sniperking.eatradish.runtime">
<application>
<provider
android:authorities="${applicationId}.ese"
android:name="com.sniperking.runtime.InstallRunTime"
android:enabled="true"
android:multiprocess="true"
android:exported="false"/>
</application>
</manifest>
Copy the code
- Write a ContentProvider
class InstallRunTime : ContentProvider() {
override fun insert(uri: Uri, values: ContentValues?).: Uri? {
return null
}
override fun query(
uri: Uri,
projection: Array<out String>? , selection:String? , selectionArgs:Array<out String>? , sortOrder:String?).: Cursor? {
return null
}
override fun onCreate(a): Boolean {
valapplication = context!! .applicationContextas Application
ActivityBuilder.INSTANCE.init(application)
return true
}
override fun update(
uri: Uri,
values: ContentValues? , selection:String? , selectionArgs:Array<out String>?: Int {
return 0
}
override fun delete(uri: Uri, selection: String? , selectionArgs:Array<out String>?: Int {
return 0
}
override fun getType(uri: Uri): String? {
return null}}Copy the code
Nothing else is handled here, just the onCreate() method, which initializes the framework so that the user can inject the framework without being aware of it, conveniently.