├── .gitignore ├── LICENSE ├── README.md ├── _config.yml ├── app-debug.apk ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── amalbit │ │ └── asynclayout │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── amalbit │ │ │ └── asynclayout │ │ │ └── LazyLayoutInflatorActivity.java │ └── res │ │ ├── layout │ │ ├── activity_layout_inflation.xml │ │ └── layout_dummy.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── amalbit │ └── asynclayout │ └── ExampleUnitTest.java ├── build.gradle └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the ART/Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | out/ 15 | 16 | # Gradle files 17 | .gradle/ 18 | build/ 19 | 20 | app/build/* 21 | 22 | # Local configuration file (sdk path, etc) 23 | local.properties 24 | 25 | # Proguard folder generated by Eclipse 26 | proguard/ 27 | 28 | # Log Files 29 | *.log 30 | 31 | # Android Studio Navigation editor temp files 32 | .navigation/ 33 | 34 | # Android Studio captures folder 35 | captures/ 36 | 37 | # Intellij 38 | *.iml 39 | .idea/workspace.xml 40 | .idea/tasks.xml 41 | .idea/gradle.xml 42 | .idea/dictionaries 43 | .idea/libraries 44 | 45 | # Keystore files 46 | *.jks 47 | 48 | # External native build folder generated in Android Studio 2.2 and later 49 | .externalNativeBuild 50 | 51 | # Google Services (e.g. APIs or Firebase) 52 | google-services.json 53 | 54 | # Freeline 55 | freeline.py 56 | freeline/ 57 | freeline_project_description.json -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Amal Chandran 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Problem 2 | 3 | Every app developer might have faced this issue. You get the design and mock up from the designer. You start the UI and implement all the animation your desginers need. Now when you start writing your business logic and integrate all your moving parts, you come across drops in your animations. You have no clue whats wrong. 4 | 5 | You break your head and run all kinds of profiling tools on your code during tight deadlines to ship your product. All this boils down to adding more load to your UI thread, when your UI thread is loaded, the choreographer drops frames. This is ripps ur animation off completely. 6 | 7 | ## Solution: 8 | 9 | When iOS and Andorid platforms were developed, the mobile devices at that time had limited computing power. So the UI rendering is only done on UI thread at one go. With the current generation phones, having multi cores, its not effectively used. 10 | 11 | To maintain the 60fps on your phones android released [Async layout inflator](https://developer.android.com/reference/android/support/v4/view/AsyncLayoutInflater.html). This is intended for parts of the UI that are created lazily or in response to user interactions. This allows the UI thread to continue to be responsive & animate while the relatively heavy inflate is being performed. 12 | 13 | ## Demo 14 | 15 | Check out the demo app which has a loader. Try inflating views with and without asynchronous layout to see the difference for yourself. 16 | 17 | 18 | ```markdown 19 | LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); 20 | View view = inflater.inflate(R.layout.layout_dummy, null); 21 | LinearLayout inflatedLayout = (LinearLayout) view.findViewById(R.id.layout_dummy); 22 | parentLayout.addView(inflatedLayout); 23 | ``` 24 | 25 | When you load normally, there will be flicker on the loader. With async, this flicker is reduced to great extent. 26 | [Demo video of layoutinflation wint async](https://youtu.be/ZETNOieGZLA) 27 | 28 | # Sample data: 29 | Inflating 100 layouts took 1004 milli seconds without async inflation and 557 milli seconds with it. 30 | 31 | Do try the poc to feel the difference. 32 | 33 | 34 | ## iOS 35 | 36 | For iOS take a look at https://github.com/facebook/AsyncDisplayKit . 37 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /app-debug.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amalChandran/asyncLayoutInflator/994b9f328c1f334cc0afd3b0bad1b50982a48b05/app-debug.apk -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the ART/Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | out/ 15 | 16 | # Gradle files 17 | .gradle/ 18 | build/ 19 | 20 | build/* 21 | 22 | # Local configuration file (sdk path, etc) 23 | local.properties 24 | 25 | # Proguard folder generated by Eclipse 26 | proguard/ 27 | 28 | # Log Files 29 | *.log 30 | 31 | # Android Studio Navigation editor temp files 32 | .navigation/ 33 | 34 | # Android Studio captures folder 35 | captures/ 36 | 37 | # Intellij 38 | *.iml 39 | .idea/workspace.xml 40 | .idea/tasks.xml 41 | .idea/gradle.xml 42 | .idea/dictionaries 43 | .idea/libraries 44 | 45 | # Keystore files 46 | *.jks 47 | 48 | # External native build folder generated in Android Studio 2.2 and later 49 | .externalNativeBuild 50 | 51 | # Google Services (e.g. APIs or Firebase) 52 | google-services.json 53 | 54 | # Freeline 55 | freeline.py 56 | freeline/ 57 | freeline_project_description.json -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 25 5 | buildToolsVersion "24.0.3" 6 | defaultConfig { 7 | applicationId "com.amalbit.asynclayout" 8 | minSdkVersion 16 9 | targetSdkVersion 25 10 | versionCode 1 11 | versionName "1.0" 12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(dir: 'libs', include: ['*.jar']) 24 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 25 | exclude group: 'com.android.support', module: 'support-annotations' 26 | }) 27 | compile 'com.android.support:appcompat-v7:25.1.1' 28 | testCompile 'junit:junit:4.12' 29 | } 30 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/amal.chandran/Documents/Tools/adt-bundle-mac-x86_64-20140702/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/amalbit/asynclayout/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.amalbit.asynclayout; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) public class ExampleInstrumentedTest { 18 | @Test public void useAppContext() throws Exception { 19 | // Context of the app under test. 20 | Context appContext = InstrumentationRegistry.getTargetContext(); 21 | 22 | assertEquals("com.amalbit.asynclayout", appContext.getPackageName()); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 9 | 10 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/amalbit/asynclayout/LazyLayoutInflatorActivity.java: -------------------------------------------------------------------------------- 1 | package com.amalbit.asynclayout; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | import android.support.annotation.Nullable; 6 | import android.support.v4.view.AsyncLayoutInflater; 7 | import android.support.v7.app.AppCompatActivity; 8 | import android.support.v7.widget.SwitchCompat; 9 | import android.util.Log; 10 | import android.view.LayoutInflater; 11 | import android.view.View; 12 | import android.view.ViewGroup; 13 | import android.widget.Button; 14 | import android.widget.CompoundButton; 15 | import android.widget.EditText; 16 | import android.widget.LinearLayout; 17 | 18 | /** 19 | * Created by amal.chandran on 19/09/16. 20 | */ 21 | public class LazyLayoutInflatorActivity extends AppCompatActivity implements View.OnClickListener{ 22 | 23 | private LinearLayout parentLayout; 24 | private Button btnAdd; 25 | private Button btnRemove; 26 | private EditText edtCount; 27 | private SwitchCompat switchAsync; 28 | 29 | private boolean isAsyncOn = false; 30 | 31 | @Override 32 | protected void onCreate(@Nullable Bundle savedInstanceState) { 33 | super.onCreate(savedInstanceState); 34 | setContentView(R.layout.activity_layout_inflation); 35 | bindViews(); 36 | addListners(); 37 | } 38 | 39 | private void bindViews(){ 40 | parentLayout = (LinearLayout) findViewById(R.id.layout_parent); 41 | btnAdd = (Button) findViewById(R.id.btn_add); 42 | btnRemove = (Button) findViewById(R.id.btn_remove); 43 | edtCount = (EditText) findViewById(R.id.edt_viewcount); 44 | switchAsync = (SwitchCompat) findViewById(R.id.switch1); 45 | } 46 | 47 | private void addListners(){ 48 | btnAdd.setOnClickListener(this); 49 | btnRemove.setOnClickListener(this); 50 | switchAsync.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { 51 | @Override 52 | public void onCheckedChanged(CompoundButton compoundButton, boolean b) { 53 | isAsyncOn = b; 54 | } 55 | }); 56 | } 57 | @Override 58 | public void onClick(View v) { 59 | switch (v.getId()){ 60 | case R.id.btn_add: 61 | String number = edtCount.getText().toString(); 62 | if(number.isEmpty())number = "refactor1"; 63 | int count = Integer.parseInt(number); 64 | loadInsaneNumberOfViews(count); 65 | break; 66 | case R.id.btn_remove: 67 | parentLayout.removeAllViews(); 68 | break; 69 | } 70 | } 71 | 72 | private void loadInsaneNumberOfViews(int count){ 73 | for(int i = 0; i < count; i++){ 74 | log(System.currentTimeMillis()+""); 75 | if(!isAsyncOn) { 76 | LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); 77 | View view = inflater.inflate(R.layout.layout_dummy, null); 78 | LinearLayout inflatedLayout = (LinearLayout) view.findViewById(R.id.layout_dummy); 79 | parentLayout.addView(inflatedLayout); 80 | }else { 81 | AsyncLayoutInflater asyncInflator = new AsyncLayoutInflater(this); 82 | asyncInflator.inflate(R.layout.layout_dummy, null, new AsyncLayoutInflater.OnInflateFinishedListener() { 83 | @Override 84 | public void onInflateFinished(View view, int resid, ViewGroup parent) { 85 | LinearLayout inflatedLayout = (LinearLayout) view.findViewById(R.id.layout_dummy); 86 | parentLayout.addView(inflatedLayout); 87 | } 88 | }); 89 | } 90 | } 91 | } 92 | 93 | private void log(String log){ 94 | Log.i(LazyLayoutInflatorActivity.class.getSimpleName(), ""+log); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_layout_inflation.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 18 | 19 | 23 | 24 | 27 | 28 | 36 | 37 |