Hire the author: Dennis M
Shape Loading View Android Image Source unsplash.com.
Here is the GitHub link for this project shape-loading-android.
Introduction
Everybody will agree that an app will get incredible interest from users when it has a great design. If you disagree try publishing an app that has an awful UI and see what happens. The huge majority of apps in the Play Store use the normal progress bar or spinner to show progress. Nonetheless, some high-quality apps innovate with nice animations to show the user that he should wait. Let’s see how to make a different loading animation using different shapes and colours.
In this article, we will create a shape loading view to indicate the progress of a task in android. The shape loading view will be inside activity and also within a dialogue box depending on the user’s preference. We will use Java programming language for the development of this android project.
This project will offer insights into the working of shape loading view in android. It will help the reader to get a better understanding of the inner-machinery that goes behind implementing such features.
Photo Preview


Glossary
Progress Bar Android ProgressBar is a visual watch that shows some progress. The Android Progress bar shows a bar representing completion. It is helpful because it gives the user an idea of ​​when to complete their task. Using ProgressBar is a good practice for the user experience as it reflects the progress of the given task (such as downloading a photo) to the user.
Steps:
Step 1: Adding required dependencies
Firstly, go to the app-level build.gradle file and add the following dependency:
dependencies { | |
compile fileTree(dir: 'libs', include: ['*.jar']) | |
compile 'com.android.support:appcompat-v7:25.3.1' | |
compile project(':shapeloading') | |
} |
Secondly, the build.gradle file will be as shown below:
apply plugin: 'com.android.library' | |
android { | |
compileSdkVersion 25 | |
buildToolsVersion '25.0.0' | |
defaultConfig { | |
minSdkVersion 9 | |
targetSdkVersion 30 | |
versionCode 1 | |
versionName "1.0.3" | |
} | |
buildTypes { | |
release { | |
minifyEnabled false | |
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' | |
} | |
} | |
} | |
dependencies { | |
compile fileTree(dir: 'libs', include: ['*.jar']) | |
compile 'com.nineoldandroids:library:2.4.0' | |
} |
Step 2: Creating the activity_main.xml file
Now, come to your activity_main.xml file which is responsible for designing the layout of the app and it’s located in the layout folder under the res folder. Change your default to Linear Layout then add additional attributes like this:
<LinearLayout | |
xmlns:android="http://schemas.android.com/apk/res/android" | |
xmlns:app="http://schemas.android.com/apk/res-auto" | |
android:orientation="vertical" | |
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" | |
android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin" | |
android:paddingRight="@dimen/activity_horizontal_margin" | |
android:paddingTop="@dimen/activity_vertical_margin" | |
android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity"> | |
<Button | |
android:id="@+id/button1" | |
android:text="View" | |
android:layout_width="fill_parent" | |
android:layout_height="wrap_content" /> | |
<Button | |
android:id="@+id/button2" | |
android:text="Dialog" | |
android:layout_width="fill_parent" | |
android:layout_height="wrap_content" /> | |
</LinearLayout> |
As shown above, the activity_main.xml is connected to two layouts which are activity_view_demo.xml and activity_dialog_demo.xml. Go to the layout folder in the res folder and create a new layout called activity_view_demo.xml and add the following code as shown below.
<RelativeLayout 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" | |
android:paddingBottom="@dimen/activity_vertical_margin" | |
android:paddingLeft="@dimen/activity_horizontal_margin" | |
android:paddingRight="@dimen/activity_horizontal_margin" | |
android:paddingTop="@dimen/activity_vertical_margin" | |
tools:context="com.dennism.ViewDemoActivity"> | |
<com.dennism.widget.LoadingView | |
android:id="@+id/loadView" | |
android:layout_width="wrap_content" | |
android:layout_height="wrap_content" | |
android:layout_centerInParent="true" | |
app:loadingText="Loading..." /> | |
</RelativeLayout> |
Create another layout called activity_dialog_demo.xml and add the following code as shown below.
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" | |
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" | |
android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin" | |
android:paddingRight="@dimen/activity_horizontal_margin" | |
android:paddingTop="@dimen/activity_vertical_margin" | |
android:paddingBottom="@dimen/activity_vertical_margin" | |
tools:context="com.dennism.DialogDemoActivity"> | |
<Button | |
android:id="@+id/button1" | |
android:text="show" | |
android:layout_centerInParent="true" | |
android:layout_width="fill_parent" | |
android:layout_height="wrap_content" /> | |
</RelativeLayout> |
Step 3: Creating the LoadingView.java file
The startLoading function will be the first to implement with the help of the constructor LoadingView. Then usedObject animator in the upThrow function to enable the different shapes to get the bouncing effect as shown below. Create another object called mFreeFallRunnable which is responsible for rotating the different shapes using the x and y-axis. Use the setVisibity function to display and hide the progress bar when necessary. Finally, the freeFall function uses the AnimatorSet object to give the progress bar the downfall effect.
package com.dennism.widget; | |
import android.annotation.TargetApi; | |
import android.content.Context; | |
import android.content.res.TypedArray; | |
import android.os.Build; | |
import android.text.TextUtils; | |
import android.util.AttributeSet; | |
import android.view.LayoutInflater; | |
import android.view.View; | |
import android.view.animation.AccelerateInterpolator; | |
import android.view.animation.DecelerateInterpolator; | |
import android.widget.ImageView; | |
import android.widget.LinearLayout; | |
import android.widget.TextView; | |
import com.dennism.shapeloading.R; | |
import com.nineoldandroids.animation.Animator; | |
import com.nineoldandroids.animation.AnimatorSet; | |
import com.nineoldandroids.animation.ObjectAnimator; | |
import com.nineoldandroids.view.ViewHelper; | |
public class LoadingView extends LinearLayout { | |
private static final int ANIMATION_DURATION = 500; | |
private static final float FACTOR = 1.2f; | |
private static float mDistance = 200; | |
private ShapeLoadingView mShapeLoadingView; | |
private ImageView mIndicationIm; | |
private TextView mLoadTextView; | |
private int mTextAppearance; | |
private String mLoadText; | |
private AnimatorSet mUpAnimatorSet; | |
private AnimatorSet mDownAnimatorSet; | |
private boolean mStopped = false; | |
private int mDelay; | |
public LoadingView(Context context) { | |
super(context); | |
init(context, null); | |
} | |
public LoadingView(Context context, AttributeSet attrs) { | |
super(context, attrs); | |
init(context, attrs); | |
} | |
@TargetApi(Build.VERSION_CODES.HONEYCOMB) | |
public LoadingView(Context context, AttributeSet attrs, int defStyleAttr) { | |
super(context, attrs, defStyleAttr); | |
init(context, attrs); | |
} | |
@TargetApi(Build.VERSION_CODES.LOLLIPOP) | |
public LoadingView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { | |
super(context, attrs, defStyleAttr, defStyleRes); | |
init(context, attrs); | |
} | |
private void init(Context context, AttributeSet attrs) { | |
setOrientation(VERTICAL); | |
mDistance = dip2px(context, 54f); | |
LayoutInflater.from(context).inflate(R.layout.load_view, this, true); | |
mShapeLoadingView = (ShapeLoadingView) findViewById(R.id.shapeLoadingView); | |
mIndicationIm = (ImageView) findViewById(R.id.indication); | |
mLoadTextView = (TextView) findViewById(R.id.promptTV); | |
ViewHelper.setScaleX(mIndicationIm, 0.2f); | |
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.LoadingView); | |
String loadText = typedArray.getString(R.styleable.LoadingView_loadingText); | |
int textAppearance = typedArray.getResourceId(R.styleable.LoadingView_loadingText, -1); | |
mDelay = typedArray.getInteger(R.styleable.LoadingView_delay, 80); | |
typedArray.recycle(); | |
if (textAppearance != -1) { | |
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { | |
mLoadTextView.setTextAppearance(textAppearance); | |
} else { | |
mLoadTextView.setTextAppearance(getContext(), textAppearance); | |
} | |
} | |
setLoadingText(loadText); | |
} | |
private int dip2px(Context context, float dipValue) { | |
final float scale = context.getResources().getDisplayMetrics().density; | |
return (int) (dipValue * scale + 0.5f); | |
} | |
@Override | |
protected void onFinishInflate() { | |
super.onFinishInflate(); | |
if (getVisibility() == VISIBLE) { | |
startLoading(mDelay); | |
} | |
} | |
private Runnable mFreeFallRunnable = new Runnable() { | |
@Override | |
public void run() { | |
ViewHelper.setRotation(mShapeLoadingView, 180f); | |
ViewHelper.setTranslationY(mShapeLoadingView, 0f); | |
ViewHelper.setScaleX(mIndicationIm, 0.2f); | |
mStopped = false; | |
freeFall(); | |
} | |
}; | |
private void startLoading(long delay) { | |
if (mDownAnimatorSet != null && mDownAnimatorSet.isRunning()) { | |
return; | |
} | |
this.removeCallbacks(mFreeFallRunnable); | |
if (delay > 0) { | |
this.postDelayed(mFreeFallRunnable, delay); | |
} else { | |
this.post(mFreeFallRunnable); | |
} | |
} | |
@Override | |
protected void onDetachedFromWindow() { | |
super.onDetachedFromWindow(); | |
stopLoading(); | |
} | |
private void stopLoading() { | |
mStopped = true; | |
if (mUpAnimatorSet != null) { | |
if (mUpAnimatorSet.isRunning()) { | |
mUpAnimatorSet.cancel(); | |
} | |
mUpAnimatorSet.removeAllListeners(); | |
for (Animator animator : mUpAnimatorSet.getChildAnimations()) { | |
animator.removeAllListeners(); | |
} | |
mUpAnimatorSet = null; | |
} | |
if (mDownAnimatorSet != null) { | |
if (mDownAnimatorSet.isRunning()) { | |
mDownAnimatorSet.cancel(); | |
} | |
mDownAnimatorSet.removeAllListeners(); | |
for (Animator animator : mDownAnimatorSet.getChildAnimations()) { | |
animator.removeAllListeners(); | |
} | |
mDownAnimatorSet = null; | |
} | |
this.removeCallbacks(mFreeFallRunnable); | |
} | |
@Override | |
public void setVisibility(int visibility) { | |
this.setVisibility(visibility, mDelay); | |
} | |
public void setVisibility(int visibility, int delay) { | |
super.setVisibility(visibility); | |
if (visibility == View.VISIBLE) { | |
startLoading(delay); | |
} else { | |
stopLoading(); | |
} | |
} | |
public void setDelay(int delay) { | |
mDelay = delay; | |
} | |
public int getDelay() { | |
return mDelay; | |
} | |
public void setLoadingText(CharSequence loadingText) { | |
if (TextUtils.isEmpty(loadingText)) { | |
mLoadTextView.setVisibility(GONE); | |
} else { | |
mLoadTextView.setVisibility(VISIBLE); | |
} | |
mLoadTextView.setText(loadingText); | |
} | |
public CharSequence getLoadingText(){ | |
return mLoadTextView.getText(); | |
} | |
public void upThrow() { | |
if (mUpAnimatorSet == null) { | |
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(mShapeLoadingView, "translationY", mDistance, 0); | |
ObjectAnimator scaleIndication = ObjectAnimator.ofFloat(mIndicationIm, "scaleX", 1f, 0.2f); | |
ObjectAnimator objectAnimator1 = null; | |
switch (mShapeLoadingView.getShape()) { | |
case SHAPE_RECT: | |
objectAnimator1 = ObjectAnimator.ofFloat(mShapeLoadingView, "rotation", 0, 180); | |
break; | |
case SHAPE_CIRCLE: | |
objectAnimator1 = ObjectAnimator.ofFloat(mShapeLoadingView, "rotation", 0, 180); | |
break; | |
case SHAPE_TRIANGLE: | |
objectAnimator1 = ObjectAnimator.ofFloat(mShapeLoadingView, "rotation", 0, 180); | |
break; | |
} | |
mUpAnimatorSet = new AnimatorSet(); | |
mUpAnimatorSet.playTogether(objectAnimator, objectAnimator1, scaleIndication); | |
mUpAnimatorSet.setDuration(ANIMATION_DURATION); | |
mUpAnimatorSet.setInterpolator(new DecelerateInterpolator(FACTOR)); | |
mUpAnimatorSet.addListener(new Animator.AnimatorListener() { | |
@Override | |
public void onAnimationStart(Animator animation) { | |
} | |
@Override | |
public void onAnimationEnd(Animator animation) { | |
if (!mStopped) { | |
freeFall(); | |
} | |
} | |
@Override | |
public void onAnimationCancel(Animator animation) { | |
} | |
@Override | |
public void onAnimationRepeat(Animator animation) { | |
} | |
}); | |
} | |
mUpAnimatorSet.start(); | |
} | |
/** | |
* whereabouts | |
*/ | |
public void freeFall() { | |
if (mDownAnimatorSet == null) { | |
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(mShapeLoadingView, "translationY", 0, mDistance); | |
ObjectAnimator scaleIndication = ObjectAnimator.ofFloat(mIndicationIm, "scaleX", 0.2f, 1f); | |
mDownAnimatorSet = new AnimatorSet(); | |
mDownAnimatorSet.playTogether(objectAnimator, scaleIndication); | |
mDownAnimatorSet.setDuration(ANIMATION_DURATION); | |
mDownAnimatorSet.setInterpolator(new AccelerateInterpolator(FACTOR)); | |
mDownAnimatorSet.addListener(new Animator.AnimatorListener() { | |
@Override | |
public void onAnimationStart(Animator animation) { | |
} | |
@Override | |
public void onAnimationEnd(Animator animation) { | |
if (!mStopped) { | |
mShapeLoadingView.changeShape(); | |
upThrow(); | |
} | |
} | |
@Override | |
public void onAnimationCancel(Animator animation) { | |
} | |
@Override | |
public void onAnimationRepeat(Animator animation) { | |
} | |
}); | |
} | |
mDownAnimatorSet.start(); | |
} | |
} |
Step 4: Creating the ShapeLoadingDialog.java file
You will use the shapeLoadingDialog constructor to make the Dialog box work effectively as shown below.
package com.dennism.widget; | |
import android.app.Dialog; | |
import android.content.Context; | |
import android.content.DialogInterface; | |
import android.os.Bundle; | |
import android.view.View; | |
import com.dennism.shapeloading.R; | |
public class ShapeLoadingDialog extends Dialog{ | |
private LoadingView mLoadingView; | |
private Builder mBuilder; | |
private ShapeLoadingDialog(Builder builder) { | |
super(builder.mContext, R.style.custom_dialog); | |
mBuilder = builder; | |
setCancelable(mBuilder.mCancelable); | |
setCanceledOnTouchOutside(mBuilder.mCanceledOnTouchOutside); | |
} | |
@Override | |
protected void onCreate(Bundle savedInstanceState) { | |
super.onCreate(savedInstanceState); | |
setContentView(R.layout.layout_dialog); | |
mLoadingView = (LoadingView) findViewById(R.id.loadView); | |
mLoadingView.setDelay(mBuilder.mDelay); | |
mLoadingView.setLoadingText(mBuilder.mLoadText); | |
setOnDismissListener(new DialogInterface.OnDismissListener() { | |
@Override | |
public void onDismiss(DialogInterface dialog) { | |
mLoadingView.setVisibility(View.GONE); | |
} | |
}); | |
} | |
@Override | |
public void show() { | |
super.show(); | |
mLoadingView.setVisibility(View.VISIBLE); | |
} | |
public Builder getBuilder() { | |
return mBuilder; | |
} | |
public static class Builder{ | |
private Context mContext; | |
private int mDelay = 80; | |
private CharSequence mLoadText; | |
private boolean mCancelable = true; | |
private boolean mCanceledOnTouchOutside = true; | |
public Builder(Context context) { | |
mContext = context; | |
} | |
public Builder delay(int delay) { | |
mDelay = delay; | |
return this; | |
} | |
public Builder loadText(CharSequence loadText) { | |
mLoadText = loadText; | |
return this; | |
} | |
public Builder loadText(int resId) { | |
mLoadText = mContext.getString(resId); | |
return this; | |
} | |
public Builder cancelable(boolean cancelable) { | |
mCancelable = cancelable; | |
mCanceledOnTouchOutside = cancelable; | |
return this; | |
} | |
public Builder canceledOnTouchOutside(boolean canceledOnTouchOutside) { | |
mCanceledOnTouchOutside = canceledOnTouchOutside; | |
return this; | |
} | |
public ShapeLoadingDialog build(){ | |
return new ShapeLoadingDialog(this); | |
} | |
public ShapeLoadingDialog show(){ | |
ShapeLoadingDialog dialog = build(); | |
dialog.show(); | |
return dialog; | |
} | |
} | |
} |
Step 5: Creating the ShapeLoadingView.java file
This activity will only be able to work by linking it to the few activities above and by using the several functions in place. It is also where we are changing the different shapes, circle, triangle by using the changeShape function. Lastly, the duration of how each shape changes to the next shape is dependent on the magic number.
package com.dennism.widget; | |
import android.annotation.TargetApi; | |
import android.content.Context; | |
import android.graphics.Canvas; | |
import android.graphics.Paint; | |
import android.graphics.Path; | |
import android.os.Build; | |
import android.util.AttributeSet; | |
import android.view.View; | |
import com.dennism.shapeloading.R; | |
import com.nineoldandroids.animation.ArgbEvaluator; | |
public class ShapeLoadingView extends View { | |
/** | |
* Draw a circle with the Sebel curve | |
*/ | |
private static final float mMagicNumber = 0.55228475f; | |
private static final float genhao3 = 1.7320508075689f; | |
private static final float mTriangle2Circle =0.25555555f; | |
private Shape mShape = Shape.SHAPE_CIRCLE; | |
private ArgbEvaluator mArgbEvaluator=new ArgbEvaluator(); | |
private int mTriangleColor ; | |
private int mCircleColor ; | |
private int mRectColor ; | |
public ShapeLoadingView(Context context) { | |
super(context); | |
init(context); | |
} | |
public ShapeLoadingView(Context context, AttributeSet attrs) { | |
super(context, attrs); | |
init(context); | |
} | |
public ShapeLoadingView(Context context, AttributeSet attrs, int defStyleAttr) { | |
super(context, attrs, defStyleAttr); | |
init(context); | |
} | |
@TargetApi(Build.VERSION_CODES.LOLLIPOP) | |
public ShapeLoadingView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { | |
super(context, attrs, defStyleAttr, defStyleRes); | |
init(context); | |
} | |
private void init(Context context) { | |
mTriangleColor = getColor(context, R.color.triangle); | |
mCircleColor =getColor(context, R.color.circle); | |
mRectColor = getColor(context, R.color.rect); | |
mPaint = new Paint(); | |
mPaint.setColor(mTriangleColor); | |
mPaint.setAntiAlias(true); | |
mPaint.setStyle(Paint.Style.FILL_AND_STROKE); | |
} | |
public boolean mIsLoading = false; | |
private Paint mPaint; | |
private float mControlX = 0; | |
private float mControlY = 0; | |
private float mAnimPercent; | |
@Override | |
protected void onDraw(Canvas canvas) { | |
super.onDraw(canvas); | |
if(getVisibility()==GONE){ | |
return; | |
} | |
// FIXME: 15/6/15 Animation to be optimized | |
switch (mShape) { | |
case SHAPE_TRIANGLE: | |
if (mIsLoading) { | |
mAnimPercent += 0.1611113; | |
int color= (int) mArgbEvaluator.evaluate(mAnimPercent,mTriangleColor,mCircleColor); | |
mPaint.setColor(color); | |
// triangle to circle | |
Path path = new Path(); | |
path.moveTo(relativeXFromView(0.5f), relativeYFromView(0f)); | |
if (mAnimPercent >= 1) { | |
mShape = Shape.SHAPE_CIRCLE; | |
mIsLoading = false; | |
mAnimPercent=1; | |
} | |
float controlX = mControlX - relativeXFromView(mAnimPercent* mTriangle2Circle) | |
* genhao3; | |
float controlY = mControlY - relativeYFromView(mAnimPercent* mTriangle2Circle); | |
path.quadTo(relativeXFromView(1) - controlX, controlY, relativeXFromView(0.5f + genhao3 / 4), relativeYFromView(0.75f)); | |
path.quadTo(relativeXFromView(0.5f), relativeYFromView(0.75f + 2 * mAnimPercent* mTriangle2Circle), relativeXFromView(0.5f - genhao3 / 4), relativeYFromView(0.75f)); | |
path.quadTo(controlX, controlY, relativeXFromView(0.5f), relativeYFromView(0f)); | |
path.close(); | |
canvas.drawPath(path, mPaint); | |
invalidate(); | |
} else { | |
Path path = new Path(); | |
mPaint.setColor(mTriangleColor); | |
path.moveTo(relativeXFromView(0.5f), relativeYFromView(0f)); | |
path.lineTo(relativeXFromView(1), relativeYFromView(genhao3 / 2f)); | |
path.lineTo(relativeXFromView(0), relativeYFromView(genhao3/2f)); | |
mControlX = relativeXFromView(0.5f - genhao3 / 8.0f); | |
mControlY = relativeYFromView(3 / 8.0f); | |
mAnimPercent = 0; | |
path.close(); | |
canvas.drawPath(path, mPaint); | |
} | |
break; | |
case SHAPE_CIRCLE: | |
if (mIsLoading) { | |
float magicNumber = mMagicNumber + mAnimPercent; | |
mAnimPercent += 0.12; | |
if (magicNumber + mAnimPercent >= 1.9f) { | |
mShape = Shape.SHAPE_RECT; | |
mIsLoading = false; | |
} | |
int color= (int) mArgbEvaluator.evaluate(mAnimPercent,mCircleColor,mRectColor); | |
mPaint.setColor(color); | |
Path path = new Path(); | |
path.moveTo(relativeXFromView(0.5f), relativeYFromView(0f)); | |
path.cubicTo(relativeXFromView(0.5f + magicNumber / 2), relativeYFromView(0f), | |
relativeXFromView(1), relativeYFromView(0.5f - magicNumber / 2), | |
relativeXFromView(1f), relativeYFromView(0.5f)); | |
path.cubicTo( | |
relativeXFromView(1), relativeXFromView(0.5f + magicNumber / 2), | |
relativeXFromView(0.5f + magicNumber / 2), relativeYFromView(1f), | |
relativeXFromView(0.5f), relativeYFromView(1f)); | |
path.cubicTo(relativeXFromView(0.5f - magicNumber / 2), relativeXFromView(1f), | |
relativeXFromView(0), relativeYFromView(0.5f + magicNumber / 2), | |
relativeXFromView(0f), relativeYFromView(0.5f)); | |
path.cubicTo(relativeXFromView(0f), relativeXFromView(0.5f - magicNumber / 2), | |
relativeXFromView(0.5f - magicNumber / 2), relativeYFromView(0), | |
relativeXFromView(0.5f), relativeYFromView(0f)); | |
path.close(); | |
canvas.drawPath(path, mPaint); | |
invalidate(); | |
} else { | |
mPaint.setColor(mCircleColor); | |
Path path = new Path(); | |
float magicNumber = mMagicNumber; | |
path.moveTo(relativeXFromView(0.5f), relativeYFromView(0f)); | |
path.cubicTo(relativeXFromView(0.5f + magicNumber / 2), 0, | |
relativeXFromView(1), relativeYFromView(magicNumber / 2), | |
relativeXFromView(1f), relativeYFromView(0.5f)); | |
path.cubicTo( | |
relativeXFromView(1), relativeXFromView(0.5f + magicNumber / 2), | |
relativeXFromView(0.5f + magicNumber / 2), relativeYFromView(1f), | |
relativeXFromView(0.5f), relativeYFromView(1f)); | |
path.cubicTo(relativeXFromView(0.5f - magicNumber / 2), relativeXFromView(1f), | |
relativeXFromView(0), relativeYFromView(0.5f + magicNumber / 2), | |
relativeXFromView(0f), relativeYFromView(0.5f)); | |
path.cubicTo(relativeXFromView(0f), relativeXFromView(0.5f - magicNumber / 2), | |
relativeXFromView(0.5f - magicNumber / 2), relativeYFromView(0), | |
relativeXFromView(0.5f), relativeYFromView(0f)); | |
mAnimPercent = 0; | |
path.close(); | |
canvas.drawPath(path, mPaint); | |
} | |
break; | |
case SHAPE_RECT: | |
if (mIsLoading) { | |
mAnimPercent += 0.15; | |
if (mAnimPercent >= 1) { | |
mShape = Shape.SHAPE_TRIANGLE; | |
mIsLoading = false; | |
mAnimPercent = 1; | |
} | |
int color= (int) mArgbEvaluator.evaluate(mAnimPercent,mRectColor,mTriangleColor); | |
mPaint.setColor(color); | |
Path path = new Path(); | |
path.moveTo(relativeXFromView(0.5f * mAnimPercent), 0); | |
path.lineTo(relativeYFromView(1 - 0.5f * mAnimPercent), 0); | |
float distanceX = (mControlX) * mAnimPercent; | |
float distanceY = (relativeYFromView(1f) - mControlY) * mAnimPercent; | |
path.lineTo(relativeXFromView(1f) - distanceX, relativeYFromView(1f) - distanceY); | |
path.lineTo(relativeXFromView(0f) + distanceX, relativeYFromView(1f) - distanceY); | |
path.close(); | |
canvas.drawPath(path, mPaint); | |
invalidate(); | |
} else { | |
mPaint.setColor(mRectColor); | |
mControlX = relativeXFromView(0.5f - genhao3 / 4); | |
mControlY = relativeYFromView(0.75f); | |
Path path = new Path(); | |
path.moveTo(relativeXFromView(0f), relativeYFromView(0f)); | |
path.lineTo(relativeXFromView(1f), relativeYFromView(0f)); | |
path.lineTo(relativeXFromView(1f), relativeYFromView(1f)); | |
path.lineTo(relativeXFromView(0f), relativeYFromView(1f)); | |
path.close(); | |
mAnimPercent = 0; | |
canvas.drawPath(path, mPaint); | |
} | |
break; | |
} | |
} | |
private float relativeXFromView(float percent) { | |
return getWidth() * percent; | |
} | |
private float relativeYFromView(float percent) { | |
return getHeight() * percent; | |
} | |
public void changeShape() { | |
mIsLoading = true; | |
invalidate(); | |
} | |
public void setShape(Shape shape){ | |
mIsLoading = true; | |
mShape = shape; | |
invalidate(); | |
} | |
public enum Shape { | |
SHAPE_TRIANGLE, SHAPE_RECT, SHAPE_CIRCLE | |
} | |
@Override | |
public void setVisibility(int visibility) { | |
super.setVisibility(visibility); | |
if(visibility==VISIBLE){ | |
invalidate(); | |
} | |
} | |
public Shape getShape() { | |
return mShape; | |
} | |
private int getColor(Context context, int id) { | |
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { | |
return context.getColor(id); | |
} else { | |
return context.getResources().getColor(id); | |
} | |
} | |
} |
Step 6: Creating the DialogDemoActivity.java file
Use the ScrollShow set functions as shown below so as to enable the use of Header movement in the android application.
package com.dennism; | |
import android.support.v7.app.ActionBarActivity; | |
import android.os.Bundle; | |
import android.view.Menu; | |
import android.view.MenuItem; | |
import android.view.View; | |
import com.dennism.widget.ShapeLoadingDialog; | |
public class DialogDemoActivity extends ActionBarActivity { | |
private ShapeLoadingDialog shapeLoadingDialog; | |
@Override | |
protected void onCreate(Bundle savedInstanceState) { | |
super.onCreate(savedInstanceState); | |
setContentView(R.layout.activity_dialog_demo); | |
shapeLoadingDialog = new ShapeLoadingDialog.Builder(this) | |
.loadText("Loading...") | |
.build(); | |
findViewById(R.id.button1).setOnClickListener(new View.OnClickListener() { | |
@Override | |
public void onClick(View v) { | |
shapeLoadingDialog.show(); | |
} | |
}); | |
} | |
@Override | |
public boolean onCreateOptionsMenu(Menu menu) { | |
// Inflate the menu; this adds items to the action bar if it is present. | |
// getMenuInflater().inflate(R.menu.menu_dialog_demo, menu); | |
return true; | |
} | |
@Override | |
public boolean onOptionsItemSelected(MenuItem item) { | |
// Handle action bar item clicks here. The action bar will | |
// automatically handle clicks on the Home/Up button, so long | |
// as you specify a parent activity in AndroidManifest.xml. | |
int id = item.getItemId(); | |
//noinspection SimplifiableIfStatement | |
if (id == R.id.action_settings) { | |
return true; | |
} | |
return super.onOptionsItemSelected(item); | |
} | |
} |
Step 7: Creating the ViewDemoActivity.java file
Go to the onCreate function as shown below. It is responsible for making objects move according to a preset program like rotating the different shapes.
package com.dennism; | |
import android.support.v7.app.ActionBarActivity; | |
import android.os.Bundle; | |
import android.view.Menu; | |
import android.view.MenuItem; | |
public class ViewDemoActivity extends ActionBarActivity { | |
@Override | |
protected void onCreate(Bundle savedInstanceState) { | |
super.onCreate(savedInstanceState); | |
setContentView(R.layout.activity_view_demo); | |
} | |
@Override | |
public boolean onCreateOptionsMenu(Menu menu) { | |
// Inflate the menu; this adds items to the action bar if it is present. | |
// getMenuInflater().inflate(R.menu.menu_view_demo, menu); | |
return true; | |
} | |
@Override | |
public boolean onOptionsItemSelected(MenuItem item) { | |
// Handle action bar item clicks here. The action bar will | |
// automatically handle clicks on the Home/Up button, so long | |
// as you specify a parent activity in AndroidManifest.xml. | |
int id = item.getItemId(); | |
//noinspection SimplifiableIfStatement | |
if (id == R.id.action_settings) { | |
return true; | |
} | |
return super.onOptionsItemSelected(item); | |
} | |
} |
Step 8: Creating the MainActivity.java file
Now, come to your MainActivity.java file. We’ll declare two buttons to display ViewDemoActivity and DialogDemoActivity. The other important function to use in the main activity is the onCreateOptionsMenu function. It is responsible for inflating the menu; this adds items to the action bar if it is present.
Finally, your final MainActivity.java will be like this.
package com.dennism; | |
import android.content.Intent; | |
import android.support.v7.app.ActionBarActivity; | |
import android.os.Bundle; | |
import android.view.Menu; | |
import android.view.MenuItem; | |
import android.view.View; | |
public class MainActivity extends ActionBarActivity { | |
@Override | |
protected void onCreate(Bundle savedInstanceState) { | |
super.onCreate(savedInstanceState); | |
setContentView(R.layout.activity_main); | |
findViewById(R.id.button1).setOnClickListener(new View.OnClickListener() { | |
@Override | |
public void onClick(View v) { | |
MainActivity.this.startActivity(new Intent(MainActivity.this,ViewDemoActivity.class)); | |
} | |
}); | |
findViewById(R.id.button2).setOnClickListener(new View.OnClickListener() { | |
@Override | |
public void onClick(View v) { | |
MainActivity.this.startActivity(new Intent(MainActivity.this,DialogDemoActivity.class)); | |
} | |
}); | |
} | |
@Override | |
public boolean onCreateOptionsMenu(Menu menu) { | |
// Inflate the menu; this adds items to the action bar if it is present. | |
getMenuInflater().inflate(R.menu.menu_main, menu); | |
return true; | |
} | |
@Override | |
public boolean onOptionsItemSelected(MenuItem item) { | |
// Handle action bar item clicks here. The action bar will | |
// automatically handle clicks on the Home/Up button, so long | |
// as you specify a parent activity in AndroidManifest.xml. | |
int id = item.getItemId(); | |
//noinspection SimplifiableIfStatement | |
if (id == R.id.action_settings) { | |
return true; | |
} | |
return super.onOptionsItemSelected(item); | |
} | |
} |
Above all, use the onOptionsItemSelected function which will Handle action bar item clicks here. The action bar will automatically handle clicks on the Home/Up button, so long as you specify a parent activity in AndroidManifest.xml.
Future Directions
Finally, the future directions involve displaying the shape loading view inside an app that requires the progress bar to be displayed while waiting for the next step to process; therefore making it able for the user to proceed to the next step successfully.
Learning Strategies and Tools
The most useful tool used for acquiring critical information for the development of this project is the android developer manual. Zeroing in on the shape loading view is responsible for displaying ecstatically the app is in progress. Instead of the spinner progress bar which is commonly used.
In conclusion, we have learned about how your app can use shape loading view in android. Activities now have to implement the different functions in their respective activities to implement the shape loading view in android.
Reflective Analysis
Figuring out how to utilize shape loading view in android was a great learning experience. It is an astonishing instrument that makes the comprehension ScrollView endpoints simple
One of its most common uses in android is used to show activity statuses such as status analysis or file downloading etc. On Android, by default, the progress bar will be displayed as a rotating wheel but if we want it to be displayed as a horizontal bar then we need to use the style attribute as horizontal.
In conclusion, I spent 72 hours finishing the project and the blog. Eventually, everything is available in this GitHub repository.
Link to the previous post: https://blog.ldtalentwork.com/2020/09/16/how-to-check-internet-connection-programatically-on-android-from-a-button-click-in-kotlin
That’s all for this tutorial!
Initially in android, I was used to using the default progress bar while waiting for an action to be executed. With this skills I am now able to create a more attractive progress bar with shapes. This knowledge can also be used in creating the Splash Screen. The splash screen is the first first page one screen upon launching an android application. Creating a good flash screen with this knowledge will attract the users attention.