8.4.3 Android Animation Collection - Property Animation - First Encounter
Category Android Basic Introduction Tutorial
Introduction:
This section introduces the third type of animation in Android—Property Animation (Property Animation). In the previous section 8.4.2 Android Animation Collection - Tween Animation, when setting transition animations for Fragments, it was mentioned that the types of animations used by Fragments in the v4 package and the app package are different. The v4 package uses Animation, while the app package uses Animator.
Animation, the general animation, includes the Frame Animation and Tween Animation we learned earlier! Animator is the Property Animation that we will discuss in this section!
Regarding Property Animation, the expert Guo Lin has written three excellent summary articles, which are highly praised and there is no need to reinvent the wheel. However, we will still go through the basics here, with most content referenced from the following three articles:
Android Property Animation Complete Guide (Part 1), Basic Usage of Property Animation
Android Property Animation Complete Guide (Part 3), Usage of Interpolator and ViewPropertyAnimator
These articles are highly recommended, or you can skip this article and go directly to the above three articles.
Of course, if you prefer to listen to my detailed explanation, you are also welcome. Let's start with this section's content.
1. Concept of Property Animation
No more talking, let's jump to the pictures, straightforward and violent~
2. Simple Usage of ValueAnimator
Usage Process:
- Call the ofInt(), ofFloat(), or ofObject() static methods of ValueAnimator to create a ValueAnimator instance.
- Call the setXxx methods of the instance to set the animation duration, interpolation method, repeat count, etc.
- Call the addUpdateListener method to add an AnimatorUpdateListener listener. In this listener, you can obtain the values calculated by ValueAnimator and apply them to the specified object.
- Call the start() method to start the animation! Additionally, we can see that both ofInt and ofFloat have a parameter like float/int... values, which means multiple values can be set.
Usage Example:
Code Implementation:
Layout file: activity_main.xml, very simple, four buttons, one ImageView
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/ly_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/btn_one"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Animation 1" />
<Button
android:id="@+id/btn_two"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Animation 2" />
<Button
android:id="@+id/btn_three"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Animation 3" />
<Button
android:id="@+id/btn_four"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Animation 4" />
<ImageView
android:id="@+id/img_babi"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="@mipmap/img_babi" />
</LinearLayout>
Next is MainActivity.java. First, a method to modify the View's position is needed. Here, moveView() is called to set the starting coordinates and dimensions.
Then, four animations are defined: linear movement, scaling, rotation with transparency, and circular rotation.
Finally, the corresponding animations are triggered by buttons.
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private Button btn_one;
private Button btn_two;
private Button btn_three;
private Button btn_four;
private LinearLayout ly_root;
private ImageView img_babi;
private int width;
private int height;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bindViews();
}
private void bindViews() {
ly_root = (LinearLayout) findViewById(R.id.ly_root);
btn_one = (Button) findViewById(R.id.btn_one);
btn_two = (Button) findViewById(R.id.btn_two);
btn_three = (Button) findViewById(R.id.btn_three);
btn_four = (Button) findViewById(R.id.btn_four);
img_babi = (ImageView) findViewById(R.id.img_babi);
btn_one.setOnClickListener(this);
btn_two.setOnClickListener(this);
btn_three.setOnClickListener(this);
btn_four.setOnClickListener(this);
img_babi.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_one:
lineAnimator();
break;
case R.id.btn_two:
scaleAnimator();
break;
case R.id.btn_three:
raAnimator();
break;
case R.id.btn_four:
circleAnimator();
break;
case R.id.img_babi:
Toast.makeText(MainActivity.this, "Indeed, coder-pig~", Toast.LENGTH_SHORT).show();
break;
}
}
// Method to modify the ImageView's position
private void moveView(View view, int rawX, int rawY) {
int left = rawX - img_babi.getWidth() / 2;
int top = rawY - img_babi.getHeight();
int width = left + view.getWidth();
int height = top + view.getHeight();
view.layout(left, top, width, height);
}
// Define property animation methods:
// Move along a trajectory equation
private void lineAnimator() {
width = ly_root.getWidth();
height = ly_root.getHeight();
ValueAnimator xValue = ValueAnimator.ofInt(height, 0, height / 4, height / 2, height / 4 * 3, height);
xValue.setDuration(3000L);
xValue.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
// Trajectory equation x = width / 2
int y = (Integer) animation.getAnimatedValue();
int x = width / 2;
moveView(img_babi, x, y);
}
});
xValue.setInterpolator(new LinearInterpolator());
xValue.start();
}
// Scale effect
private void scaleAnimator() {
// This is intentionally done with two to help understand how to use combined animations
final float scale = 0.5f;
AnimatorSet scaleSet = new AnimatorSet();
ValueAnimator valueAnimatorSmall = ValueAnimator.ofFloat(1.0f, scale);
valueAnimatorSmall.setDuration(500);
ValueAnimator valueAnimatorLarge = ValueAnimator.ofFloat(scale, 1.0f);
valueAnimatorLarge.setDuration(500);
valueAnimatorSmall.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float scale = (Float) animation.getAnimatedValue();
img_babi.setScaleX(scale);
img_babi.setScaleY(scale);
}
});
valueAnimatorLarge.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float scale = (Float) animation.getAnimatedValue();
img_babi.setScaleX(scale);
img_babi.setScaleY(scale);
}
});
scaleSet.play(valueAnimatorLarge).after(valueAnimatorSmall);
scaleSet.start();
// Actually, one would be enough
// ValueAnimator vValue = ValueAnimator.ofFloat(1.0f, 0.6f, 1.2f, 1.0f, 0.6f, 1.2f, 1.0f);
// vValue.setDuration(1000L);
// vValue.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
// @Override
// public void onAnimationUpdate(ValueAnimator animation) {
// float scale = (Float) animation.getAnimatedValue();
// img_babi.setScaleX(scale);
// img_babi.setScaleY(scale);
// }
// });
// vValue.setInterpolator(new LinearInterpolator());
// vValue.start();
}
// Rotate while changing opacity
private void raAnimator() {
ValueAnimator rValue = ValueAnimator.ofInt(0, 360);
rValue.setDuration(1000L);
rValue.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int rotateValue = (Integer) animation.getAnimatedValue();
img_babi.setRotation(rotateValue);
float fractionValue = animation.getAnimatedFraction();
img_babi.setAlpha(fractionValue);
}
});
rValue.setInterpolator(new DecelerateInterpolator());
rValue.start();
}
// Circular rotation
protected void circleAnimator() {
width = ly_root.getWidth();
height = ly_root.getHeight();
final int R = width / 4;
ValueAnimator tValue = ValueAnimator.ofFloat(0,
(float) (2.0f * Math.PI));
tValue.setDuration(1000);
tValue.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
// Parametric equation of a circle x = R * sin(t) y = R * cos(t)
float t = (Float) animation.getAnimatedValue();
int x = (int) (R * Math.sin(t) + width / 2);
int y = (int) (R * Math.cos(t) + height / 2);
moveView(img_babi, x, y);
}
});
tValue.setInterpolator(new DecelerateInterpolator());
tValue.start();
}
}
The process is quite straightforward: first, create a ValueAnimator object, call ValueAnimator.ofInt/ofFloat to obtain it, then set the duration of the animation, addUpdateListener to add an AnimatorUpdateListener event listener, and use the parameter animation's getAnimatedValue() to get the current value. With this value, we can modify some properties of the View to create the so-called animation effect. Next, set the setInterpolator animation rendering mode and finally call start() to start the animation playback.
Wow, linear equations, parametric equations of a circle, I'm starting to feel overwhelmed. These are things from advanced mathematics, and I've forgotten even trigonometric functions as a failed student...
Example referenced from GitHub: MoveViewValueAnimator
3. Simple Use of ObjectAnimator
Compared to ValueAnimator, ObjectAnimator is easier to use. With this class, we can directly animate any property of any object! Yes, any object, not just View objects. It continuously assigns values to a property of an object, and then decides how to display based on the changes in the property values. For example, to set an animation for a TextView: ObjectAnimator.ofFloat(textview, "alpha", 1f, 0f); It looks for the get and set methods corresponding to the property name being passed, not the property value itself! If you don't believe me, you can look for the "alpha" property in the TextView source code.
Alright, let's use ObjectAnimator to achieve four types of tween animations.
Running Effect Diagram:
Code Implementation:
The layout is directly reused from the previous one, with a button added and the ImageView replaced with a TextView. I won't post the code here, just the MainActivity.java part, which is mostly similar.
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private Button btn_one;
private Button btn_two;
private Button btn_three;
private Button btn_four;
private Button btn_five;
private LinearLayout ly_root;
private TextView tv_pig;
private int height;
private ObjectAnimator animator1;
private ObjectAnimator animator2;
private ObjectAnimator animator3;
private ObjectAnimator animator4;
private AnimatorSet animSet;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bindViews();
initAnimator();
}
private void bindViews() {
ly_root = (LinearLayout) findViewById(R.id.ly_root);
btn_one = (Button) findViewById(R.id.btn_one);
btn_two = (Button) findViewById(R.id.btn_two);
btn_three = (Button) findViewById(R.id.btn_three);
btn_four = (Button) findViewById(R.id.btn_four);
btn_five = (Button) findViewById(R.id.btn_five);
tv_pig = (TextView) findViewById(R.id.tv_pig);
height = ly_root.getHeight();
btn_one.setOnClickListener(this);
btn_two.setOnClickListener(this);
btn_three.setOnClickListener(this);
btn_four.setOnClickListener(this);
btn_five.setOnClickListener(this);
tv_pig.setOnClickListener(this);
}
// Initialize animation
private void initAnimator() {
animator1 = ObjectAnimator.ofFloat(tv_pig, "alpha", 1f, 0f, 1f, 0f, 1f);
animator2 = ObjectAnimator.ofFloat(tv_pig, "rotation", 0f, 360f, 0f);
animator3 = ObjectAnimator.ofFloat(tv_pig, "scaleX", 2f, 4f, 1f, 0.5f, 1f);
animator4 = ObjectAnimator.ofFloat(tv_pig, "translationY", height / 8, -100, height / 2);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_one:
animator1.setDuration(3000l);
animator1.start();
break;
case R.id.btn_two:
animator2.setDuration(3000l);
animator2.start();
break;
case R.id.btn_three:
animator3.setDuration(3000l);
animator3.start();
break;
case R.id.btn_four:
animator4.setDuration(3000l);
animator4.start();
break;
case R.id.btn_five:
// Combine previous animations
animSet = new AnimatorSet();
animSet.play(animator4).with(animator3).with(animator2).after(animator1);
animSet.setDuration(5000l);
animSet.start();
break;
case R.id.tv_pig:
Toast.makeText(MainActivity.this, "Indeed, coder-pig~", Toast.LENGTH_SHORT).show();
break;
}
}
}
Usage is also very simple, the combined animations we mentioned above will be discussed below~
4. Combined Animations and AnimatorListener
From the two examples above, we have experienced combined animations using the AnimatorSet class!
We call the play()
method and pass in the first animation to start, which returns a Builder class to us:
Next, we can use the four methods provided by the Builder to combine other animations:
- after (Animator anim) Insert the current animation after the passed animation
- after (long delay) Delay the current animation by the specified milliseconds
- before (Animator anim) Insert the current animation before the passed animation
- with (Animator anim) Execute the current animation and the passed animation simultaneously
It's simple. Next, let's talk about animation event listeners. Above, we used AnimatorUpdateListener, which calls the onAnimationUpdate
method when the value changes.
In addition to this event, there are animation state listeners: AnimatorListener. We can call addListener
to add a listener and override the following four callback methods:
- onAnimationStart(): Animation starts
- onAnimationRepeat(): Animation repeats
- onAnimationEnd(): Animation ends
- onAnimationCancel(): Animation is canceled
If you use AnimatorListener, you need to override all four methods. Similar to the gesture section earlier, Android provides an adapter class: AnimatorListenerAdapter, which has already implemented each interface method. So, we can just write one callback method here.
5. Writing Animations in XML
Writing animations in XML might take a bit longer than Java code, but reusing them is much easier! The corresponding XML tags are: <animator>
, <objectAnimator>
, <set>
.
Relevant attribute explanations are as follows:
- android:ordering: Specifies the playback order of the animations: sequentially (one after another), together (simultaneously)
- android:duration: Duration of the animation
- android:propertyName="x": The "x" here, remember "alpha" above? The object loading the animation needs to define
getX
andsetX
methods, whichobjectAnimator
uses to modify the object's values. - android:valueFrom="1": Initial value of the animation
- android:valueTo="0": Final value of the animation
- android:valueType="floatType": Data type of the value
Example usage:
① Smooth transition animation from 0 to 100:
<animator xmlns:android="http://schemas.android.com/apk/res/android"
android:valueFrom="0"
android:valueTo="100"
android:valueType="intType"/>
② Animation to change the alpha property of a view from 1 to 0:
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:valueFrom="1"
android:valueTo="0"
android:valueType="floatType"
android:propertyName="alpha"/>
③ Set animation usage demonstration:
<set android:ordering="sequentially" >
<set>
<objectAnimator
android:duration="500"
android:propertyName="x"
android:valueTo="400"
android:valueType="intType" />
<objectAnimator
android:duration="500"
android:propertyName="y"
android:valueTo="300"
android:valueType="intType" />
</set>
<objectAnimator
android:duration="500"
android:propertyName="alpha"
android:valueTo="1f" />
</set>
Loading our animation file:
AnimatorSet set = (AnimatorSet)AnimatorInflater.loadAnimator(mContext,
R.animator.property_animator);
animator.setTarget(view);
animator.start();
6. Sample Code Download:
Summary:
>
Alright, this section has covered the basics of using property animations in Android. I hope you've got it. The content is relatively simple, and the examples are interesting, so I believe you'll enjoy them. That's all for now, thank you~
Thanks to Guo Shen for the article~
> -1.0 Android Basic Introduction Tutorial
-1.0.1 2015 Latest Android Basic Introduction Tutorial Contents
-1.1 Background and System Architecture Analysis
-1.2 Development Environment Setup
-1.2.1 Developing Android Apps with Eclipse + ADT + SDK
-1.2.2 Developing Android Apps with Android Studio
-1.3 Solving SDK Update Issues
-1.4 Genymotion Emulator Installation
-1.5.1 Git Tutorial: Basic Operations on Local Repository
-1.5.2 Git: Setting Up a Remote Repository on GitHub
-1.6 How to Use the "Nine Patch" Image
-1.7 Interface Prototype Design
-1.8 Project Source Analysis (Various Files, Resource Access)
-1.9 Android Application Signing and Packaging
-1.11 Decompiling APK to Retrieve Code & Resources
-2.1 Concepts of View and ViewGroup
-2.2.1 LinearLayout (Linear Layout)
-2.2.2 RelativeLayout (Relative Layout)
-2.2.3 TableLayout (Table Layout)
-2.2.4 FrameLayout (Frame Layout)
-2.2.5 GridLayout (Grid Layout)
-2.2.6 AbsoluteLayout (Absolute Layout)
-2.3.1 Detailed Explanation of TextView (Text Field)
-2.3.2 Detailed Explanation of EditText (Input Field)
-2.3.5 RadioButton (Radio Button) & Checkbox (Checkbox)
-2.3.6 ToggleButton and Switch
-2.3.7 ProgressBar (Progress Bar)
-2.3.9 RatingBar (Star Rating Bar)
-2.4.1 ScrollView (Scroll Bar)
-2.4.2 Date & Time Components (Part 1)
-2.4.3 Date & Time Components (Part 2)
-2.4.5 Simple Usage of ListView
-2.4.6 Optimization of BaseAdapter
-2.4.7 Focus Issues with ListView
-2.4.8 Solving Checkbox Misalignment in ListView
-2.4.9 Data Update Issues in ListView
-2.5.0 Building a Reusable Custom BaseAdapter
-2.5.1 Implementing Multiple Item Layouts in ListView
-2.5.2 Basic Usage of GridView (Grid View)
-2.5.3 Basic Usage of Spinner (List Option Box)
-2.5.4 Basic Usage of AutoCompleteTextView (Auto-Complete Text Field)
-2.5.5 Basic Usage of ExpandableListView (Collapsible List)
-2.5.6 Basic Usage of ViewFlipper (Flip View)
-2.5.8 Detailed Explanation of Notification (Status Bar Notification)
-2.5.9 Detailed Explanation of AlertDialog (Dialog Box)
-2.6.0 Basic Usage of Other Common Dialogs
-2.6.1 Basic Usage of PopupWindow (Floating Box)
-2.6.3 Simple Usage of ViewPager
-2.6.4 Simple Usage of DrawerLayout (Official Side Menu)
-3.1.1 Event Handling Mechanism Based on Listeners
-3.2 Event Handling Mechanism Based on Callbacks
-3.3 Analysis of Handler Message Passing Mechanism
-3.4 TouchListener vs OnTouchEvent + Multi-Touch
-3.5 Listening for Content Changes in EditText
-3.6 Responding to System Settings Changes (Configuration Class)
-3.7 AsyncTask Asynchronous Task
-4.1.1 Activity: Getting Started
-4.3.1 BroadcastReceiver: Basic
-4.3.2 BroadcastReceiver: Advanced
-4.4.1 ContentProvider: Introduction
-4.4.2 ContentProvider: Advanced - Document Provider
-4.5.2 Passing Complex Data with Intent
-5.1 Basic Overview of Fragment
-5.2.1 Fragment Example: Bottom Navigation Bar Implementation (Method 1)
-5.2.2 Fragment Example: Bottom Navigation Bar Implementation (Method 2)
- 5.2.3 Fragment Example Walkthrough - Bottom Navigation Bar Implementation (Method 3)
- 5.2.4 Fragment Example Walkthrough - Bottom Navigation Bar + ViewPager Swipe to Switch Pages
- 5.2.5 Fragment Example Walkthrough - Simple Implementation of News (Shopping) App List Fragment
- 6.1 Data Storage and Access - File Storage and Reading/Writing
- 6.2 Data Storage and Access - SharedPreferences for Saving User Preferences
- 6.3.1 Data Storage and Access - Introduction to SQLite Database
- 6.3.2 Data Storage and Access - Further Exploration of SQLite Database
- 7.1.1 Android Network Programming Essentials and Learning HTTP Protocol
- 7.1.2 Android HTTP Request Headers and Response Headers Study
- 7.1.3 Android HTTP Request Methods: HttpURLConnection
- 7.1.4 Android HTTP Request Methods: HttpClient
- 7.2.1 Android XML Data Parsing
- 7.2.2 Android JSON Data Parsing
- 7.3.1 Android File Upload
- 7.3.2 Android File Download (1)
- 7.3.3 Android File Download (2)
- 7.4 Android Calling WebService
- 7.5.1 WebView (Web View) Basic Usage
- 7.5.2 WebView and JavaScript Interaction Basics
- 7.5.3 Important Considerations for WebView in Android 4.4 and Later
- 7.5.4 WebView File Download
- 7.5.5 WebView Cache Issues
- 7.5.6 WebView Handling Webpage Error Code Information
- 7.6.1 Socket Learning Network Basics Preparation
- 7.6.2 Socket Communication Based on TCP Protocol (1)
- 7.6.3 Socket Communication Based on TCP Protocol (2)
- 7.6.4 Socket Communication Based on UDP Protocol
- 8.1.1 Summary of 13 Drawable Types in Android Part 1
- 8.1.2 Summary of 13 Drawable Types in Android Part 2
- 8.1.3 Summary of 13 Drawable Types in Android Part 3
- 8.2.1 Bitmap (Bitmap) Comprehensive Analysis Part 1
- 8.2.2 OOM Issues Caused by Bitmap
- 8.3.1 Detailed Explanation of Three Drawing Tool Classes
- 8.3.2 Drawing Class Practical Examples
- 8.3.3 Paint API - MaskFilter (Mask)
- 8.3.4 Paint API - Xfermode and PorterDuff Detailed Explanation (Part 1)
- 8.3.5 Paint API - Xfermode and PorterDuff Detailed Explanation (Part 2)
- 8.3.6 Paint API - Xfermode and PorterDuff Detailed Explanation (Part 3)
- 8.3.7 Paint API - Xfermode and PorterDuff Detailed Explanation (Part 4)
- 8.3.8 Paint API - Xfermode and PorterDuff Detailed Explanation (Part 5)
- 8.3.9 Paint API - ColorFilter (Color Filter) (1/3)
- 8.3.10 Paint API - ColorFilter (Color Filter) (2/3)
- 8.3.11 Paint API - ColorFilter (Color Filter) (3/3)
- 8.3.12 Paint API - PathEffect (Path Effect)
- 8.3.13 Paint API - Shader (Image Rendering)
- 8.3.14 Paint Enum/Constant Values and ShadowLayer Shadow Effects
- 8.3.15 Paint API - Typeface (Font Style)
- 8.3.16 Canvas API Detailed Explanation (Part 1)
- 8.3.17 Canvas API Detailed Explanation (Part 2) Clipping Methods Collection
- 8.3.18 Canvas API Detailed Explanation (Part 3) Matrix and drawBitmapMesh
- 8.4.1 Android Animation Collection - Frame Animation
- 8.4.2 Android Animation Collection - Tween Animation
- 8.4.3 Android Animation Collection - Property Animation - Introduction
- 8.4.4 Android Animation Collection - Property Animation - Further Exploration
- 9.1 Using SoundPool to Play Sound Effects (Duang~)
- 9.2 MediaPlayer for Audio and Video Playback
- 9.3 Using Camera to Take Photos
- 9.4 Using MediaRecord for Audio Recording
- 10.1 TelephonyManager (Telephony Manager)
- 10.2 SmsManager (SMS Manager)
- 10.3 AudioManager (Audio Manager)
- 10.4 Vibrator (Vibrator)
- 10.5 AlarmManager (Alarm Service)
- 10.6 PowerManager (Power Service)
- 10.7 WindowManager (Window Management Service)
- 10.8 LayoutInflater (Layout Service)
- 10.9 WallpaperManager (Wallpaper Manager)
- 10.10 Sensor Topic (1) - Introduction
- 10.11 Sensor Topic (2) - Orientation Sensor
- 10.12 Sensor Topic (3) - Accelerometer/Gyroscope Sensor
- 10.12 Sensor Topic (4) - Understanding Other Sensors
- 10.14 Android GPS Introduction
- 11.0 "2015 Latest Android Basic Beginner's Guide" Completion Celebration
- 12.1 Android Practice: DrySister Viewing Girls App (First Edition) - Project Setup and Simple Implementation
- 12.2 DrySister Viewing Girls App (First Edition) - Parsing Backend Data
- 12.3 DrySister View Girls App (Version 1) – 3. Image Loading Optimization (Writing a Small Image Cache Framework)
12.4 DrySister View Girls App (Version 1) – 4. Adding Data Caching (Integrating SQLite)
12.5 DrySister View Girls App (Version 1) – 5. Code Review, Adjustments, and Logging Class Writing