I'm Iman Dolatkia, Experienced Android developer and software engineer.
🎮 Game and football lover
👍 I enjoy UI/UX
📫 How to reach me:
✔️telegram ID: https://t.me/iman_dolatkia
✔️email: [email protected]
create your custom themes and change them dynamically with ripple animation
License: Apache License 2.0
I'm Iman Dolatkia, Experienced Android developer and software engineer.
🎮 Game and football lover
👍 I enjoy UI/UX
📫 How to reach me:
✔️telegram ID: https://t.me/iman_dolatkia
✔️email: [email protected]
I create a bottomsheetbehavior and add inside a recyclerview contains theme list.
But i click item recyclerview to change theme ThemeManager animated theme on over bottomsheet and bottomsheet has Gone.
How to destroy finished activity theme manager ?????
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.samprojre/com.example.samprojre.screens.main.MainActivity}: java.lang.IllegalStateException: You need to use a Theme.AppCompat theme (or descendant) with this activity.
Currently, I am using Theme.Material3.Light.NoActionBar as a style for MainActivity and I didn't have that exception before when I extended MainActivity from AppCompatActivity
Hello again.
How to use in BottomSheetDialogFragment and AlertDialog ??
java.lang.IllegalStateException: Cannot start this animator on a detached view!
When i click change theme button, all working good but when i open any acivity and go back then click, and crashed
I try to add this using java. but suddenly i saw "binder.root." what is this? How can i syncTheme in java? i think this is important but you missing explanation???
Hi! How do I make it work on the action bar? GOOD JOB GUYS !!!
🐛 Describe the bug
The issue arises in the ThemeManager class within the application. Specifically, there seems to be an error related to class casting in the getRelativeLeft and getRelativeTop methods.
When certain conditions are met, the application crashes with a ClassCastException in the ThemeManager class.
Error:
java.lang.ClassCastException: android.view.ViewRootImpl cannot be cast to android.view.View
at com.dolatkia.animatedThemeManager.ThemeManager.getRelativeLeft(ThemeManager.kt:133)
at com.dolatkia.animatedThemeManager.ThemeManager.getRelativeLeft(ThemeManager.kt:133)
at com.dolatkia.animatedThemeManager.ThemeManager.getRelativeLeft(ThemeManager.kt:133)
at com.dolatkia.animatedThemeManager.ThemeManager.getRelativeLeft(ThemeManager.kt:133)
at com.dolatkia.animatedThemeManager.ThemeManager.getRelativeLeft(ThemeManager.kt:133)
at com.dolatkia.animatedThemeManager.ThemeManager.getRelativeLeft(ThemeManager.kt:133)
at com.dolatkia.animatedThemeManager.ThemeManager.getRelativeLeft(ThemeManager.kt:133)
at com.dolatkia.animatedThemeManager.ThemeManager.getRelativeLeft(ThemeManager.kt:133)
at com.dolatkia.animatedThemeManager.ThemeManager.getRelativeLeft(ThemeManager.kt:133)
at com.dolatkia.animatedThemeManager.ThemeManager.getRelativeLeft(ThemeManager.kt:133)
at com.dolatkia.animatedThemeManager.ThemeManager.getRelativeLeft(ThemeManager.kt:133)
at com.dolatkia.animatedThemeManager.ThemeManager.getRelativeLeft(ThemeManager.kt:133)
at com.dolatkia.animatedThemeManager.ThemeManager.getViewCoordinates(ThemeManager.kt:127)
at com.dolatkia.animatedThemeManager.ThemeManager.changeTheme(ThemeManager.kt:48)
at .Fragments.Profile.FgProfileSettings.lambda$onViewCreated$3$....-Fragments-Profile-FgProfileSettings(FgProfileSettings.java:151)
at .Fragments.Profile.FgProfileSettings$$ExternalSyntheticLambda16.onCheckedChanged(D8$$SyntheticClass:0)
at android.widget.CompoundButton.setChecked(CompoundButton.java:222)
at androidx.appcompat.widget.SwitchCompat.setChecked(SwitchCompat.java:1179)
at androidx.appcompat.widget.SwitchCompat.toggle(SwitchCompat.java:1174)
at android.widget.CompoundButton.performClick(CompoundButton.java:144)
at android.view.View.performClickInternal(View.java:7519)
at android.view.View.-$$Nest$mperformClickInternal(Unknown Source:0)
at android.view.View$PerformClick.run(View.java:29476)
at android.os.Handler.handleCallback(Handler.java:942)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:201)
at android.os.Looper.loop(Looper.java:288)
at android.app.ActivityThread.main(ActivityThread.java:7918)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936)
Fragment:
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.SwitchCompat;
import com.dolatkia.animatedThemeManager.AppTheme;
import com.dolatkia.animatedThemeManager.ThemeFragment;
import com.dolatkia.animatedThemeManager.ThemeManager;
public class FgProfileSettings extends ThemeFragment {
private SwitchCompat sw_theme;
private CmpTextView tv_theme;
private RelativeLayout rl_theme;
private View v_theme;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View this_view = inflater.inflate(R.layout.fg_profile_settings, container, false);
rl_theme = this_view.findViewById(R.id.rl_theme);
tv_theme = this_view.findViewById(R.id.tv_theme);
v_theme = this_view.findViewById(R.id.v_theme);
return this_view;
}
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
sw_theme.setOnCheckedChangeListener((buttonView, isChecked) -> {
if(isChecked) {
ThemeManager.Companion.getInstance().changeTheme(new ThemesDay(), v_theme, 600);
} else {
ThemeManager.Companion.getInstance().changeTheme(new ThemesNight(), v_theme, 600);
}
});
}
@Override
public void onResume() {
super.onResume();
}
@Override
public void syncTheme(@NonNull AppTheme appTheme) {
ITheme myAppTheme = (ITheme) appTheme;
tv_theme.setTextColor(myAppTheme.setColor(requireContext(), EnumColor.BLACK));
}
}
Interface:
public interface ITheme extends AppTheme {
int setColor(@NotNull Context context, EnumColor color);
}
ThemeDay:
public class ThemesDay implements ITheme {
public int id() { // set unique iD for each theme
return 0;
}
@Override
public int setColor(@NotNull Context context, EnumColor color) {
return Color.parseColor(getColor(color));
}
private String getColor(EnumColor color){
switch (color){
case WHITE:
return "#FFFFFF";
default:
return "#FFFFFF";
}
}
}
ThemeNight
public class ThemesNight implements ITheme {
public int id() { // set unique iD for each theme
return 0;
}
@Override
public int setColor(@NotNull Context context, EnumColor color) {
return Color.parseColor(getColor(color));
}
private String getColor(EnumColor color){
switch (color){
case WHITE:
return "#181F2A";
default:
return "#181F2A";
}
}
}
I'm normally using a viewbinding library to simplify the process and prevent memory leaks.
ViewBindingPropertyDelegate
The library wants me to pass the layout in the activity constructor as described in the README.
This makes it impossible to use with this Theme-Manager.
It would be nice if this library were compatible with ViewBindingPropertyDelegate and the best option for this would be if it's possible to pass the layout in the constructor.
My code works fine if my activity extends from AppCompatActivity, but I got this exception when extends ThemeActivity
Process: com.example.todoapp, PID: 5708
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.todoapp/com.example.todoapp.MainActivity}: java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3449)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3601)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2066)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:223)
at android.app.ActivityThread.main(ActivityThread.java:7656)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
Caused by: java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.
at android.view.ViewGroup.addViewInner(ViewGroup.java:5235)
at android.view.ViewGroup.addView(ViewGroup.java:5064)
at android.view.ViewGroup.addView(ViewGroup.java:5004)
at android.view.ViewGroup.addView(ViewGroup.java:4976)
at com.dolatkia.animatedThemeManager.ThemeActivity.setContentView(ThemeActivity.kt:59)
at com.dolatkia.animatedThemeManager.ThemeActivity.setContentView(ThemeActivity.kt:54)
at com.example.todoapp.MainActivity.onCreate(MainActivity.kt:27)
at android.app.Activity.performCreate(Activity.java:8000)
at android.app.Activity.performCreate(Activity.java:7984)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1309)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3422)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3601)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2066)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:223)
at android.app.ActivityThread.main(ActivityThread.java:7656)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
My MainActivity.kt
package com.example.todoapp
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.drawerlayout.widget.DrawerLayout
import com.dolatkia.animatedThemeManager.AppTheme
import com.dolatkia.animatedThemeManager.ThemeActivity
import com.dolatkia.animatedThemeManager.ThemeManager
import com.google.android.material.appbar.MaterialToolbar
import com.google.android.material.navigation.NavigationView
class MainActivity : ThemeActivity() {
private lateinit var drawerLayout: DrawerLayout
private lateinit var topAppBar: MaterialToolbar
private lateinit var navigationView: NavigationView
override fun getStartTheme(): AppTheme {
return LightTheme()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main) // I GOT EXCEPTION HERE
// Views
drawerLayout = findViewById(R.id.drawer)
topAppBar = findViewById(R.id.topAppBar)
navigationView = findViewById(R.id.navigation_view)
supportFragmentManager.beginTransaction().replace(R.id.fragment_container, MainFragment()).commit()
topAppBar.setNavigationOnClickListener {
drawerLayout.open()
}
navigationView.setNavigationItemSelectedListener { menuItem ->
if (menuItem.isChecked) {
false
} else {
// Handle menu item selected
when (menuItem.itemId) {
R.id.nav_main -> supportFragmentManager.beginTransaction().replace(R.id.fragment_container, MainFragment()).commit()
R.id.nav_about -> supportFragmentManager.beginTransaction().replace(R.id.fragment_container, AboutFragment()).commit()
}
menuItem.isChecked = true
drawerLayout.close()
false
}
}
}
override fun syncTheme(appTheme: AppTheme) {
val myAppTheme = appTheme as MyAppTheme
drawerLayout.rootView.setBackgroundColor(myAppTheme.firstActivityBackgroundColor(this))
}
}
My activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout 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:id="@+id/drawer"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/Widget.MaterialComponents.ActionBar.PrimarySurface"
android:fitsSystemWindows="true">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/topAppBar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:title="Main"
app:navigationIcon="@drawable/ic_baseline_menu_24"
style="@style/Widget.MaterialComponents.ActionBar.PrimarySurface"
android:background="@android:color/transparent"
android:elevation="0dp" />
</com.google.android.material.appbar.AppBarLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/fragment_container"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<com.google.android.material.navigation.NavigationView
android:id="@+id/navigation_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
app:menu="@menu/navigation_drawer" />
</androidx.drawerlayout.widget.DrawerLayout>
Can you help me get through this. Anyway thank you for an amazing library (sorry for my bad english)
how to update RecyclerView?
Thank you for your invitation
I've been looking at your team's code and I feel pretty good about it.We are very happy to introduce this project into our project
However, I found a small flaw in the fact that clicking the button during the animation might cause button state confusion
binder.button.setOnClickListener {
isNight = if(isNight){
ThemeManager.instance.reverseChangeTheme(LightTheme(), it)
false
}else{
ThemeManager.instance.changeTheme(NightTheme(), it)
true
}
...
}
fun changeTheme(
newTheme: AppTheme,
sourceCoordinate: Coordinate,
animDuration: Long,
isReverse: Boolean
) {
...
if (frontFakeThemeImageView.visibility == View.VISIBLE ||
behindFakeThemeImageView.visibility == View.VISIBLE ||
isRunningChangeThemeAnimation()
) {
return
}
...
}
I think the easy way to do that is to add a return value.Maybe not a good way to handle it
Hope your project will get better and better
Hi @imandolatkia ! First of all I have to say that this library is AMAZING, this just save my life really!!
Could you explain how to implement this feature in a recycler? You said this a long time ago but I can´t se any related video
HI @SudoDios,
It's a really good question.
1- You should synchronize the RecyclerView theme in Viewholders. I mean in the RecyclerView adapter and inonBindViewHolder method, with the use of ThemeManager.
2- After the theme is changed, if the fragment that contained RecyclerView was displaying, call notifyDataSetChanged() from its adapter.
I will add the sample in a week.
Thanks a lot !!! And again, very good job !!!
Although I easily fixed this issue, the solution should be included in the next update.
I'd give a bit of context on why this happened and how to recreate the problem.
I have a base class BaseActivity
which extends ThemeActivity
. All my Activity
classes extend BaseActivity
and so can override syncTheme()
and getStartTheme()
.
Let's say I start from MainActivity
which extends BaseActivity
and in it's onCreate()
it calls ThemeManager.init(activity, getStartTheme())
. This happens from the ThemeActivity
super class.
However when I navigate to another class say SecondActivity
, this is what happens.
onStop()
of MainActivity
is called.onCreate()
of SecondActivity
is called and this is where the ThemeManager
instance is initialized again (from the super class ThemeActivity
) and changes private activity
variable from MainActivity
to SecondActivity
. This is fine as the current Context
would be SecondActivity
.But when I navigate back to MainActivity
, this is what happens.
onStop()
for SecondActivity
is called.onRestart()
for MainActivity
is called.MainActivity
is not destroyed and remained on the heap, it just stopped and restarted. It's onCreate()
was not called and that means the activity
variable in the ThemeManager
instance that was reinstantiated when SecondActivity
was navigated to still has the private activity
variable as the SecondActivity
instance (which should have been destroyed when navigated back up). This is a resource leak.And so whenever I try to change theme in MainActivity
, I get a "Cannot run animation on a detached View" error from the ViewAnimator
.
I fixed this error by reinstantiating ThemeManager
by calling ThemeManager.init(activity, getStartTheme())
in the onRestart()
of MainActivity
and that fixed my issue.
So I feel onRestart()
should be overriden in ThemeActivity
and ThemeManager
should reinstantiated on that callback.
I did a debug on my code when carrying out the above scenario and found that issue out.
hi, thank for great lib
i want to set color from xml, not from code, did u support this feature?
I don't understand what needs to be done to save the theme next time. How can we record in onDestroy. Can you show me a preview?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.