Giter Site home page Giter Site logo

imandolatkia / android-animated-theme-manager Goto Github PK

View Code? Open in Web Editor NEW
698.0 13.0 66.0 943 KB

create your custom themes and change them dynamically with ripple animation

License: Apache License 2.0

Kotlin 100.00%
android kotlin theme ripple animation java android-library thememanager ripple-animation

android-animated-theme-manager's Introduction

Hi there 👋

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]



Anurag's GitHub stats

android-animated-theme-manager's People

Contributors

andreasroither avatar imandolatkia avatar kojofosu avatar lwj1994 avatar pelmenstar1 avatar wwdablu avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

android-animated-theme-manager's Issues

BottomSheetBehavior

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.

Activity theme

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

Cannot start anim on deAttachedView

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

Java How to use? something missing?

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???

Action bar?

Hi! How do I make it work on the action bar? GOOD JOB GUYS !!!

java.lang.ClassCastException: android.view.ViewRootImpl cannot be cast to android.view.View

🐛 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.

⚠️ Current behavior
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";
        }
    }
}

Pass layout in activity constructor

⚠️ Is your feature request related to a problem? Please describe

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.

💡 Describe the solution you'd like

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.

🤚 Do you want to develop this feature yourself?

  • Yes
  • No

The specified child already has a parent exception at setContentView

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)

Clicking the button during the animation might cause button state confusion

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

RecyclerView

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 :octocat:

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 !!!

Cannot start animation on a detached view for a resumed Activity.

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.
    Since the 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.

save the theme

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?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.