├── .gitignore ├── .idea ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── misc.xml ├── modules.xml └── runConfigurations.xml ├── LICENSE.txt ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── takeoffandroid │ │ └── shimmercontactsview │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── takeoffandroid │ │ │ └── shimmercontactsview │ │ │ ├── SampleApp.java │ │ │ ├── activities │ │ │ ├── BaseActivity.java │ │ │ ├── ContactsListActivity.java │ │ │ └── MainActivity.java │ │ │ ├── adapter │ │ │ └── ContactsListAdapter.java │ │ │ ├── contacts │ │ │ ├── ContactDTO.java │ │ │ ├── ContactsInteractor.java │ │ │ ├── ContactsInteractorImpl.java │ │ │ ├── ContactsPresenter.java │ │ │ ├── ContactsPresenterImpl.java │ │ │ └── ContactsView.java │ │ │ ├── utils │ │ │ ├── Logger.java │ │ │ └── Utils.java │ │ │ └── views │ │ │ ├── shimmer │ │ │ ├── ShimmerAdapter.java │ │ │ ├── ShimmerFrameLayout.java │ │ │ ├── ShimmerRecyclerView.java │ │ │ └── ShimmerViewHolder.java │ │ │ └── typefacehelper │ │ │ ├── DialogUtils.java │ │ │ ├── TypefaceCache.java │ │ │ └── TypefaceHelper.java │ └── res │ │ ├── drawable │ │ └── toolbar_shadow.xml │ │ ├── layout │ │ ├── activity_contacts_list.xml │ │ ├── activity_main.xml │ │ ├── lib_shimmer_viewholder.xml │ │ ├── list_item_contacts.xml │ │ └── shimmer_sample_view.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── attrs.xml │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── takeoffandroid │ └── shimmercontactsview │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── 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 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | 23 | # Proguard folder generated by Eclipse 24 | proguard/ 25 | 26 | # Log Files 27 | *.log 28 | 29 | # Android Studio Navigation editor temp files 30 | .navigation/ 31 | 32 | # Android Studio captures folder 33 | captures/ 34 | 35 | # Intellij 36 | *.iml 37 | .idea/workspace.xml 38 | .idea/tasks.xml 39 | .idea/gradle.xml 40 | .idea/dictionaries 41 | .idea/libraries 42 | 43 | # Keystore files 44 | *.jks 45 | 46 | # External native build folder generated in Android Studio 2.2 and later 47 | .externalNativeBuild 48 | 49 | # Google Services (e.g. APIs or Firebase) 50 | google-services.json 51 | 52 | # Freeline 53 | freeline.py 54 | freeline/ 55 | freeline_project_description.json -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Chandrasekar K 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so without any conditions and terms. 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | Buy Me a Coffee at ko-fi.com 3 | 4 | 5 | Why ShimmerContactsView? 6 | ------------------------- 7 | 8 | - It is the superfast view to display contacts in RecyclerView with rich material design UI. 9 | - This view can be used as a contact picker using bottom sheet dialog and activity. 10 | 11 | 12 | 13 | 14 | Demo I | Demo II 15 | -------- | --- 16 | | 17 | 18 | ### Attributes and Methods 19 | 20 | Following are the attributes and methods to initialise the demo views. 21 | 22 | | XML Attributes | Java Methods | Explanation | 23 | | ------------- | ------------ | ----------- | 24 | |```app:demo_child_count``` | ```setDemoChildCount(int)``` | Integer value that sets the number of demo views should be present in shimmer adapter | 25 | |```app:demo_layout``` | ```setDemoLayoutReference(int)``` | Layout reference to your demo view. Define your my_demo_view.xml and refer the layout reference here. | 26 | |```app:demo_layout_manager_type``` | ```setDemoLayoutManager(LayoutManagerType)``` | Layout manager of demo view. Can be one among linear_veritical or linear_horizontal or grid. | 27 | 28 | 29 | 30 | Usage 31 | -------- 32 | 33 | Define your xml as: 34 | 35 | ```xml 36 | 37 | 46 | 47 | ``` 48 | where ```@layout/layout_demo_grid``` refers to your sample layout that should be shown during loading spinner. Now on your activity onCreate, initialize the shimmer as below: 49 | 50 | ```java 51 | ShimmerRecyclerView shimmerRecycler = (ShimmerRecyclerView) findViewById(R.id.recycler_view_contacts); 52 | shimmerRecycler.showShimmerAdapter(); 53 | ``` 54 | 55 | Benchmark Test 56 | --------------- 57 | 58 | 59 | 60 | 61 | 62 | Acknowledgements 63 | ----------------- 64 | 65 | * Facebook for Shimmer Android 66 | * Thanks to ShimmerRecyclerView 67 | * TextDrawable 68 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 25 5 | buildToolsVersion "25.0.2" 6 | defaultConfig { 7 | applicationId "com.takeoffandroid.shimmercontactsview" 8 | minSdkVersion 15 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 | 23 | ext { 24 | supportLibraryVersion = '25.2.0' 25 | butterknifeVersion = "8.5.1" 26 | permissiondispatcher = "2.1.3" 27 | } 28 | 29 | 30 | dependencies { 31 | compile fileTree(dir: 'libs', include: ['*.jar']) 32 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 33 | exclude group: 'com.android.support', module: 'support-annotations' 34 | }) 35 | compile "com.android.support:appcompat-v7:$supportLibraryVersion" 36 | compile "com.android.support:recyclerview-v7:$supportLibraryVersion" 37 | 38 | compile 'com.android.support.constraint:constraint-layout:1.0.1' 39 | testCompile 'junit:junit:4.12' 40 | 41 | 42 | compile "com.jakewharton:butterknife:$butterknifeVersion" 43 | annotationProcessor "com.jakewharton:butterknife-compiler:$butterknifeVersion" 44 | 45 | compile 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1' 46 | 47 | compile "com.android.support:design:$supportLibraryVersion" 48 | 49 | 50 | compile 'com.github.hotchemi:permissionsdispatcher:2.3.1' 51 | annotationProcessor 'com.github.hotchemi:permissionsdispatcher-processor:2.3.1' 52 | 53 | } 54 | -------------------------------------------------------------------------------- /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/f22labs/Library/Android/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 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/takeoffandroid/shimmercontactsview/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.takeoffandroid.shimmercontactsview; 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) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.takeoffandroid.shimmercontactsview", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/java/com/takeoffandroid/shimmercontactsview/SampleApp.java: -------------------------------------------------------------------------------- 1 | package com.takeoffandroid.shimmercontactsview; 2 | 3 | import android.app.Application; 4 | 5 | import com.takeoffandroid.shimmercontactsview.views.typefacehelper.TypefaceHelper; 6 | 7 | public class SampleApp extends Application { 8 | @Override 9 | public void onCreate() { 10 | super.onCreate(); 11 | TypefaceHelper.initialize(this); 12 | } 13 | 14 | @Override 15 | public void onTerminate() { 16 | TypefaceHelper.destroy(); 17 | super.onTerminate(); 18 | } 19 | } -------------------------------------------------------------------------------- /app/src/main/java/com/takeoffandroid/shimmercontactsview/activities/BaseActivity.java: -------------------------------------------------------------------------------- 1 | package com.takeoffandroid.shimmercontactsview.activities; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.support.v7.widget.Toolbar; 7 | 8 | /** 9 | * Created by f22labs on 07/03/17. 10 | */ 11 | 12 | public class BaseActivity extends AppCompatActivity { 13 | 14 | @Override 15 | public void onCreate(@Nullable Bundle savedInstanceState) { 16 | super.onCreate(savedInstanceState); 17 | 18 | } 19 | 20 | public void initToolbar(Toolbar toolbar) { 21 | setSupportActionBar(toolbar); 22 | } 23 | 24 | 25 | 26 | 27 | 28 | 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/takeoffandroid/shimmercontactsview/activities/ContactsListActivity.java: -------------------------------------------------------------------------------- 1 | package com.takeoffandroid.shimmercontactsview.activities; 2 | 3 | import android.Manifest; 4 | import android.os.Bundle; 5 | import android.os.Handler; 6 | import android.support.annotation.NonNull; 7 | import android.support.v7.widget.DefaultItemAnimator; 8 | import android.support.v7.widget.DividerItemDecoration; 9 | import android.support.v7.widget.LinearLayoutManager; 10 | import android.support.v7.widget.Toolbar; 11 | import android.view.Menu; 12 | import android.view.MenuItem; 13 | 14 | import com.takeoffandroid.shimmercontactsview.R; 15 | import com.takeoffandroid.shimmercontactsview.adapter.ContactsListAdapter; 16 | import com.takeoffandroid.shimmercontactsview.contacts.ContactDTO; 17 | import com.takeoffandroid.shimmercontactsview.contacts.ContactsPresenter; 18 | import com.takeoffandroid.shimmercontactsview.contacts.ContactsPresenterImpl; 19 | import com.takeoffandroid.shimmercontactsview.contacts.ContactsView; 20 | import com.takeoffandroid.shimmercontactsview.utils.Logger; 21 | import com.takeoffandroid.shimmercontactsview.utils.Utils; 22 | import com.takeoffandroid.shimmercontactsview.views.shimmer.ShimmerRecyclerView; 23 | 24 | import java.util.ArrayList; 25 | 26 | import butterknife.BindView; 27 | import butterknife.ButterKnife; 28 | import permissions.dispatcher.NeedsPermission; 29 | import permissions.dispatcher.OnPermissionDenied; 30 | import permissions.dispatcher.OnShowRationale; 31 | import permissions.dispatcher.PermissionRequest; 32 | import permissions.dispatcher.RuntimePermissions; 33 | 34 | @RuntimePermissions 35 | public class ContactsListActivity extends BaseActivity implements ContactsView { 36 | 37 | @BindView(R.id.toolbar) 38 | Toolbar toolbar; 39 | 40 | @BindView(R.id.recycler_view_contacts) 41 | ShimmerRecyclerView recyclerViewContacts; 42 | 43 | private ContactsListAdapter mAdapter; 44 | private ContactsPresenter contactsPresenter; 45 | 46 | @Override 47 | public void onCreate(Bundle savedInstanceState) { 48 | super.onCreate(savedInstanceState); 49 | setContentView(R.layout.activity_contacts_list); 50 | ButterKnife.bind(this); 51 | 52 | initToolbar(toolbar); 53 | 54 | mAdapter = new ContactsListAdapter(ContactsListActivity.this,new ArrayList()); 55 | recyclerViewContacts.setHasFixedSize(true); 56 | recyclerViewContacts.setLayoutManager(new LinearLayoutManager(getApplicationContext())); 57 | recyclerViewContacts.addItemDecoration(new DividerItemDecoration(this, LinearLayoutManager.VERTICAL)); 58 | recyclerViewContacts.setItemAnimator(new DefaultItemAnimator()); 59 | recyclerViewContacts.setAdapter(mAdapter); 60 | 61 | contactsPresenter = new ContactsPresenterImpl(ContactsListActivity.this, this); 62 | 63 | contactsPresenter.loadContacts(ContactsListActivity.this); 64 | 65 | 66 | 67 | } 68 | 69 | 70 | @Override 71 | protected void onResume() { 72 | super.onResume(); 73 | 74 | 75 | } 76 | 77 | @Override 78 | public boolean onCreateOptionsMenu(Menu menu) { 79 | // getMenuInflater().inflate(R.menu.menu_add, menu); 80 | return true; 81 | } 82 | 83 | @Override 84 | public boolean onOptionsItemSelected(MenuItem item) { 85 | 86 | switch (item.getItemId()) { 87 | 88 | case android.R.id.home: 89 | finish(); 90 | break; 91 | 92 | } 93 | return super.onOptionsItemSelected(item); 94 | } 95 | 96 | 97 | 98 | 99 | 100 | @Override 101 | public void onContactsLoading() { 102 | 103 | recyclerViewContacts.showShimmerAdapter(); 104 | } 105 | 106 | @Override 107 | public void onContactsLoadingComplete() { 108 | 109 | new Handler().postDelayed(new Runnable() { 110 | @Override 111 | public void run() { 112 | recyclerViewContacts.hideShimmerAdapter(); 113 | } 114 | },2000); 115 | 116 | } 117 | 118 | @Override 119 | public void onContactsLoadFailure(String message) { 120 | 121 | Logger.i("loading failure"); 122 | 123 | if(message.equals(getString(R.string.permisson_error))){ 124 | 125 | ContactsListActivityPermissionsDispatcher.requestContactsPermissionWithCheck(ContactsListActivity.this); 126 | 127 | } 128 | 129 | } 130 | 131 | @Override 132 | public void onContactsLoadSuccess(ArrayList contactDTOArrayList) { 133 | 134 | Logger.i((contactDTOArrayList != null ? contactDTOArrayList.size() : 0 )+ " Loading success!"); 135 | 136 | mAdapter.updateContacts(contactDTOArrayList); 137 | 138 | } 139 | 140 | 141 | @NeedsPermission({Manifest.permission.READ_CONTACTS}) 142 | void requestContactsPermission(){ 143 | 144 | contactsPresenter = new ContactsPresenterImpl(ContactsListActivity.this, this); 145 | 146 | contactsPresenter.loadContacts(ContactsListActivity.this); 147 | } 148 | 149 | 150 | @OnPermissionDenied(Manifest.permission.READ_CONTACTS) 151 | void deniedContactsPermission() { 152 | finish(); 153 | } 154 | 155 | 156 | 157 | // @OnShowRationale({Manifest.permission.READ_CONTACTS}) 158 | // void showRationaleForContact(PermissionRequest request) { 159 | // // NOTE: Show a rationale to explain why the permission is needed, e.g. with a dialog. 160 | // // Call proceed() or cancel() on the provided PermissionRequest to continue or abort 161 | // Utils.showRationaleDialog(ContactsListActivity.this,"Need permission to access contacts", request); 162 | // } 163 | 164 | 165 | 166 | @Override 167 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { 168 | super.onRequestPermissionsResult(requestCode, permissions, grantResults); 169 | // NOTE: delegate the permission handling to generated method 170 | ContactsListActivityPermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults); 171 | } 172 | 173 | } -------------------------------------------------------------------------------- /app/src/main/java/com/takeoffandroid/shimmercontactsview/activities/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.takeoffandroid.shimmercontactsview.activities; 2 | 3 | import android.Manifest; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.os.Handler; 7 | import android.support.annotation.NonNull; 8 | import android.support.design.widget.BottomSheetDialog; 9 | import android.support.v7.app.AppCompatActivity; 10 | import android.support.v7.widget.DefaultItemAnimator; 11 | import android.support.v7.widget.DividerItemDecoration; 12 | import android.support.v7.widget.LinearLayoutManager; 13 | import android.view.View; 14 | import android.widget.Button; 15 | import android.widget.ImageView; 16 | 17 | import com.takeoffandroid.shimmercontactsview.R; 18 | import com.takeoffandroid.shimmercontactsview.adapter.ContactsListAdapter; 19 | import com.takeoffandroid.shimmercontactsview.contacts.ContactDTO; 20 | import com.takeoffandroid.shimmercontactsview.contacts.ContactsPresenter; 21 | import com.takeoffandroid.shimmercontactsview.contacts.ContactsPresenterImpl; 22 | import com.takeoffandroid.shimmercontactsview.contacts.ContactsView; 23 | import com.takeoffandroid.shimmercontactsview.utils.Logger; 24 | import com.takeoffandroid.shimmercontactsview.utils.Utils; 25 | import com.takeoffandroid.shimmercontactsview.views.shimmer.ShimmerRecyclerView; 26 | 27 | import java.util.ArrayList; 28 | 29 | import butterknife.BindView; 30 | import butterknife.ButterKnife; 31 | import butterknife.OnClick; 32 | import permissions.dispatcher.NeedsPermission; 33 | import permissions.dispatcher.OnPermissionDenied; 34 | import permissions.dispatcher.OnShowRationale; 35 | import permissions.dispatcher.PermissionRequest; 36 | import permissions.dispatcher.RuntimePermissions; 37 | 38 | @RuntimePermissions 39 | public class MainActivity extends AppCompatActivity implements ContactsView { 40 | 41 | 42 | @BindView(R.id.btn_bottom_sheet) 43 | Button btnBottomSheet; 44 | 45 | @BindView(R.id.btn_activity) 46 | Button btnActivity; 47 | 48 | private ShimmerRecyclerView recyclerViewContacts; 49 | 50 | private ContactsListAdapter mAdapter; 51 | private ContactsPresenter contactsPresenter; 52 | 53 | private BottomSheetDialog dialog; 54 | 55 | @Override 56 | protected void onCreate(Bundle savedInstanceState) { 57 | super.onCreate(savedInstanceState); 58 | setContentView(R.layout.activity_main); 59 | ButterKnife.bind(this); 60 | 61 | 62 | } 63 | 64 | 65 | @OnClick(R.id.btn_activity) 66 | void openContactsList() { 67 | 68 | Intent activityIntent = new Intent(MainActivity.this, ContactsListActivity.class); 69 | startActivity(activityIntent); 70 | } 71 | 72 | 73 | @OnClick(R.id.btn_bottom_sheet) 74 | void showContactsBottomSheet() { 75 | 76 | if (Utils.isReadContactsPermissionEnabled(MainActivity.this)) { 77 | 78 | View view = getLayoutInflater().inflate(R.layout.activity_contacts_list, null); 79 | 80 | recyclerViewContacts = (ShimmerRecyclerView) view.findViewById(R.id.recycler_view_contacts); 81 | 82 | mAdapter = new ContactsListAdapter(MainActivity.this, new ArrayList()); 83 | recyclerViewContacts.setHasFixedSize(true); 84 | recyclerViewContacts.setLayoutManager(new LinearLayoutManager(getApplicationContext())); 85 | recyclerViewContacts.addItemDecoration(new DividerItemDecoration(this, LinearLayoutManager.VERTICAL)); 86 | recyclerViewContacts.setItemAnimator(new DefaultItemAnimator()); 87 | recyclerViewContacts.setAdapter(mAdapter); 88 | 89 | 90 | if (Utils.isDismissDialog(dialog)) { 91 | return; 92 | } 93 | 94 | contactsPresenter = new ContactsPresenterImpl(MainActivity.this, this); 95 | contactsPresenter.loadContacts(MainActivity.this); 96 | 97 | dialog = new BottomSheetDialog(this, R.style.BottomSheetDialog); 98 | dialog.setContentView(view); 99 | dialog.show(); 100 | 101 | } else { 102 | 103 | MainActivityPermissionsDispatcher.requestContactsPermissionWithCheck(MainActivity.this); 104 | } 105 | } 106 | 107 | 108 | @Override 109 | public void onContactsLoading() { 110 | 111 | recyclerViewContacts.showShimmerAdapter(); 112 | } 113 | 114 | @Override 115 | public void onContactsLoadingComplete() { 116 | 117 | new Handler().postDelayed(new Runnable() { 118 | @Override 119 | public void run() { 120 | recyclerViewContacts.hideShimmerAdapter(); 121 | } 122 | }, 2000); 123 | 124 | } 125 | 126 | @Override 127 | public void onContactsLoadFailure(String message) { 128 | 129 | Logger.i("loading failure"); 130 | 131 | if (message.equals(getString(R.string.permisson_error))) { 132 | 133 | MainActivityPermissionsDispatcher.requestContactsPermissionWithCheck(MainActivity.this); 134 | 135 | } 136 | } 137 | 138 | @Override 139 | public void onContactsLoadSuccess(ArrayList contactDTOArrayList) { 140 | 141 | Logger.i((contactDTOArrayList != null ? contactDTOArrayList.size() : 0) + " Loading success!"); 142 | 143 | mAdapter.updateContacts(contactDTOArrayList); 144 | 145 | } 146 | 147 | 148 | @NeedsPermission({Manifest.permission.READ_CONTACTS}) 149 | void requestContactsPermission() { 150 | showContactsBottomSheet(); 151 | } 152 | 153 | 154 | @OnPermissionDenied(Manifest.permission.READ_CONTACTS) 155 | void deniedContactsPermission() { 156 | finish(); 157 | } 158 | 159 | 160 | 161 | @Override 162 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { 163 | super.onRequestPermissionsResult(requestCode, permissions, grantResults); 164 | // NOTE: delegate the permission handling to generated method 165 | MainActivityPermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults); 166 | } 167 | 168 | } 169 | -------------------------------------------------------------------------------- /app/src/main/java/com/takeoffandroid/shimmercontactsview/adapter/ContactsListAdapter.java: -------------------------------------------------------------------------------- 1 | package com.takeoffandroid.shimmercontactsview.adapter; 2 | 3 | import android.content.Context; 4 | import android.support.v7.widget.RecyclerView; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.widget.ImageView; 9 | import android.widget.TextView; 10 | 11 | import com.amulyakhare.textdrawable.TextDrawable; 12 | import com.amulyakhare.textdrawable.util.ColorGenerator; 13 | import com.takeoffandroid.shimmercontactsview.R; 14 | import com.takeoffandroid.shimmercontactsview.contacts.ContactDTO; 15 | 16 | import java.util.ArrayList; 17 | 18 | import butterknife.BindView; 19 | import butterknife.ButterKnife; 20 | 21 | public class ContactsListAdapter extends RecyclerView.Adapter { 22 | 23 | 24 | 25 | private ArrayList mContactsList; 26 | // private static OnItemClickListener mItemClickListener; 27 | 28 | private Context mContext; 29 | 30 | 31 | public void updateContacts(ArrayList contactDTOArrayList) { 32 | this.mContactsList = contactDTOArrayList; 33 | notifyDataSetChanged(); 34 | } 35 | 36 | 37 | public class MyViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { 38 | 39 | @BindView(R.id.txt_contact_name) 40 | TextView txtContactName; 41 | 42 | @BindView(R.id.txt_contact_mobile) 43 | TextView txtContactMobile; 44 | 45 | @BindView(R.id.img_user) 46 | ImageView imgUser; 47 | 48 | public MyViewHolder(View view) { 49 | super(view); 50 | 51 | 52 | ButterKnife.bind(this, view); 53 | 54 | view.setOnClickListener(this); 55 | 56 | } 57 | 58 | 59 | @Override 60 | public void onClick(View v) { 61 | 62 | // mItemClickListener.onItemClick(v, getAdapterPosition(), mContactsList.get(getAdapterPosition())); 63 | 64 | } 65 | 66 | 67 | } 68 | 69 | 70 | public ContactsListAdapter(Context context, ArrayList contactDTOArrayList) { 71 | this.mContactsList = contactDTOArrayList; 72 | this.mContext = context; 73 | } 74 | 75 | 76 | @Override 77 | public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 78 | View itemView = LayoutInflater.from(parent.getContext()) 79 | .inflate(R.layout.list_item_contacts, parent, false); 80 | 81 | 82 | return new MyViewHolder(itemView); 83 | 84 | } 85 | 86 | @Override 87 | public void onBindViewHolder(MyViewHolder holder, int position) { 88 | ContactDTO contactDTO = mContactsList.get(position); 89 | holder.txtContactName.setText(contactDTO.getName()); 90 | holder.txtContactMobile.setText(contactDTO.getNumber()); 91 | 92 | ColorGenerator generator = ColorGenerator.MATERIAL; // or use DEFAULT 93 | 94 | // declare the builder object once. 95 | TextDrawable.IBuilder builder = TextDrawable.builder() 96 | .beginConfig() 97 | .endConfig() 98 | .round(); 99 | 100 | // reuse the builder specs to create multiple drawables 101 | TextDrawable textDrawable = builder.build(contactDTO.getName().substring(0,1), generator.getRandomColor()); 102 | 103 | holder.imgUser.setImageDrawable(textDrawable); 104 | 105 | } 106 | 107 | @Override 108 | public int getItemCount() { 109 | return mContactsList.size(); 110 | } 111 | 112 | 113 | // public interface OnItemClickListener { 114 | // void onItemClick(View view, int position, ContactDTO contactDTO); 115 | // } 116 | // 117 | // public void SetOnItemClickListener(final OnItemClickListener mItemClickListener) { 118 | // this.mItemClickListener = mItemClickListener; 119 | // } 120 | 121 | 122 | } -------------------------------------------------------------------------------- /app/src/main/java/com/takeoffandroid/shimmercontactsview/contacts/ContactDTO.java: -------------------------------------------------------------------------------- 1 | package com.takeoffandroid.shimmercontactsview.contacts; 2 | 3 | import android.graphics.Bitmap; 4 | 5 | public class ContactDTO { 6 | 7 | private String name; 8 | private String image; 9 | private String number; 10 | private int contactID; 11 | 12 | private Bitmap photo; 13 | 14 | 15 | 16 | public String getName() { 17 | return name; 18 | } 19 | 20 | public void setName(String name) { 21 | this.name = name; 22 | } 23 | 24 | public String getImage() { 25 | return image; 26 | } 27 | 28 | public void setImage(String image) { 29 | this.image = image; 30 | } 31 | 32 | public String getNumber() { 33 | return number; 34 | } 35 | 36 | public void setNumber(String number) { 37 | this.number = number; 38 | } 39 | 40 | public int getContactID() { 41 | return contactID; 42 | } 43 | 44 | public void setContactID(int contactID) { 45 | this.contactID = contactID; 46 | } 47 | 48 | public Bitmap getPhoto() { 49 | return photo; 50 | } 51 | 52 | public void setPhoto(Bitmap photo) { 53 | this.photo = photo; 54 | } 55 | } -------------------------------------------------------------------------------- /app/src/main/java/com/takeoffandroid/shimmercontactsview/contacts/ContactsInteractor.java: -------------------------------------------------------------------------------- 1 | package com.takeoffandroid.shimmercontactsview.contacts; 2 | 3 | import android.content.Context; 4 | 5 | import java.util.ArrayList; 6 | 7 | public interface ContactsInteractor { 8 | 9 | interface OnContactLoadFinishedListener { 10 | 11 | void onContactsLoadSuccess(ArrayList contactDTOArrayList); 12 | 13 | void onContactsLoadFaiure(String message); 14 | 15 | } 16 | 17 | void loadContacts(Context context, OnContactLoadFinishedListener listener); 18 | 19 | } -------------------------------------------------------------------------------- /app/src/main/java/com/takeoffandroid/shimmercontactsview/contacts/ContactsInteractorImpl.java: -------------------------------------------------------------------------------- 1 | package com.takeoffandroid.shimmercontactsview.contacts; 2 | 3 | import android.content.Context; 4 | import android.database.Cursor; 5 | import android.net.Uri; 6 | import android.provider.ContactsContract; 7 | 8 | 9 | import com.takeoffandroid.shimmercontactsview.R; 10 | import com.takeoffandroid.shimmercontactsview.utils.Logger; 11 | import com.takeoffandroid.shimmercontactsview.utils.Utils; 12 | 13 | import java.util.ArrayList; 14 | 15 | public class ContactsInteractorImpl implements ContactsInteractor { 16 | 17 | 18 | @Override 19 | public void loadContacts(Context context, OnContactLoadFinishedListener listener) { 20 | 21 | getContactsList(context, listener); 22 | 23 | } 24 | 25 | 26 | private void getContactsList(Context context, OnContactLoadFinishedListener listener) { 27 | 28 | if (Utils.isReadContactsPermissionEnabled(context)) { 29 | ArrayList allContacts = new ArrayList(); 30 | 31 | Uri uri = ContactsContract.CommonDataKinds.Phone.CONTENT_URI; 32 | String selection = ContactsContract.Contacts.HAS_PHONE_NUMBER; 33 | Cursor cursor = context.getContentResolver().query(uri, new String[]{ContactsContract.CommonDataKinds.Phone.NUMBER, ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME, ContactsContract.CommonDataKinds.Phone._ID, ContactsContract.Contacts._ID}, selection, null, ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME + " ASC"); 34 | 35 | cursor.moveToFirst(); 36 | while (cursor.isAfterLast() == false) { 37 | 38 | ContactDTO contactDTO = new ContactDTO(); 39 | 40 | String contactNumber = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)); 41 | String contactName = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)); 42 | 43 | // String contactsImage = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.PHOTO_ID)); 44 | 45 | int phoneContactID = cursor.getInt(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone._ID)); 46 | int contactID = cursor.getInt(cursor.getColumnIndex(ContactsContract.Contacts._ID)); 47 | 48 | contactDTO.setName(contactName); 49 | contactDTO.setNumber(contactNumber); 50 | 51 | contactDTO.setContactID(contactID); 52 | // contactDTO.setImage(contactsImage); 53 | 54 | // contactDTO.setPhoto(ContactUtils.getPhotoFromContacts(context, contactID)); 55 | allContacts.add(contactDTO); 56 | 57 | cursor.moveToNext(); 58 | } 59 | 60 | 61 | cursor.close(); 62 | cursor = null; 63 | 64 | 65 | listener.onContactsLoadSuccess(allContacts); 66 | 67 | } else { 68 | 69 | listener.onContactsLoadFaiure(context.getString(R.string.permisson_error)); 70 | } 71 | 72 | } 73 | } -------------------------------------------------------------------------------- /app/src/main/java/com/takeoffandroid/shimmercontactsview/contacts/ContactsPresenter.java: -------------------------------------------------------------------------------- 1 | package com.takeoffandroid.shimmercontactsview.contacts; 2 | 3 | import android.content.Context; 4 | 5 | public interface ContactsPresenter { 6 | void loadContacts(Context context); 7 | 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/takeoffandroid/shimmercontactsview/contacts/ContactsPresenterImpl.java: -------------------------------------------------------------------------------- 1 | package com.takeoffandroid.shimmercontactsview.contacts; 2 | 3 | import android.content.Context; 4 | 5 | import java.util.ArrayList; 6 | 7 | /** 8 | * Created by f22labs on 17/03/17. 9 | */ 10 | 11 | public class ContactsPresenterImpl implements ContactsPresenter, ContactsInteractor.OnContactLoadFinishedListener { 12 | 13 | private Context mContext; 14 | private ContactsView view; 15 | private ContactsInteractor contactsInteractor; 16 | 17 | 18 | 19 | 20 | public ContactsPresenterImpl(Context context,ContactsView view) { 21 | 22 | this.mContext = context; 23 | this.view = view; 24 | this.contactsInteractor = new ContactsInteractorImpl(); 25 | } 26 | 27 | 28 | 29 | @Override 30 | public void onContactsLoadSuccess(ArrayList contactDTOArrayList) { 31 | 32 | view.onContactsLoadingComplete(); 33 | 34 | view.onContactsLoadSuccess(contactDTOArrayList); 35 | } 36 | 37 | @Override 38 | public void onContactsLoadFaiure(String message) { 39 | 40 | view.onContactsLoadingComplete(); 41 | 42 | view.onContactsLoadFailure(message); 43 | } 44 | 45 | @Override 46 | public void loadContacts(Context context) { 47 | 48 | view.onContactsLoading(); 49 | contactsInteractor.loadContacts(mContext, this); 50 | 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/src/main/java/com/takeoffandroid/shimmercontactsview/contacts/ContactsView.java: -------------------------------------------------------------------------------- 1 | package com.takeoffandroid.shimmercontactsview.contacts; 2 | 3 | 4 | import java.util.ArrayList; 5 | 6 | /** 7 | * Created by ennur on 6/25/16. 8 | */ 9 | public interface ContactsView { 10 | void onContactsLoading(); 11 | 12 | void onContactsLoadingComplete(); 13 | 14 | void onContactsLoadFailure(String message); 15 | 16 | void onContactsLoadSuccess(ArrayList contactDTOArrayList); 17 | 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/takeoffandroid/shimmercontactsview/utils/Logger.java: -------------------------------------------------------------------------------- 1 | package com.takeoffandroid.shimmercontactsview.utils; 2 | 3 | 4 | import android.content.Context; 5 | import android.util.Log; 6 | import android.widget.Toast; 7 | 8 | public class Logger { 9 | 10 | private static final String TAG = "Addo"; 11 | 12 | static String className; 13 | static String methodName; 14 | static int lineNumber; 15 | private static boolean LOG_ENABLED = true; 16 | 17 | private static String createLog( String log ) { 18 | 19 | return "[" + methodName + ":" + lineNumber + "]" + log; 20 | } 21 | 22 | private static void getMethodNames(StackTraceElement[] sElements){ 23 | className = sElements[1].getFileName(); 24 | methodName = sElements[1].getMethodName(); 25 | lineNumber = sElements[1].getLineNumber(); 26 | } 27 | 28 | public static void d(String data){ 29 | if(!LOG_ENABLED) 30 | return; 31 | getMethodNames(new Throwable ().getStackTrace()); 32 | Log.d (className, createLog (data)); 33 | } 34 | 35 | public static void v(String data){ 36 | if(!LOG_ENABLED) 37 | return; 38 | getMethodNames(new Throwable ().getStackTrace()); 39 | Log.v (className, createLog (data)); 40 | } 41 | 42 | public static void i(String data){ 43 | if(!LOG_ENABLED) 44 | return; 45 | getMethodNames(new Throwable ().getStackTrace()); 46 | Log.i (className, createLog (data)); 47 | } 48 | 49 | public static void e(String data){ 50 | if(!LOG_ENABLED) 51 | return; 52 | getMethodNames(new Throwable ().getStackTrace()); 53 | Log.e (className, createLog (data)); 54 | } 55 | 56 | public static void ToastMessage(Context context,String message){ 57 | Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); 58 | } 59 | 60 | 61 | } 62 | -------------------------------------------------------------------------------- /app/src/main/java/com/takeoffandroid/shimmercontactsview/utils/Utils.java: -------------------------------------------------------------------------------- 1 | package com.takeoffandroid.shimmercontactsview.utils; 2 | 3 | import android.Manifest; 4 | import android.content.Context; 5 | import android.content.DialogInterface; 6 | import android.content.pm.PackageManager; 7 | import android.support.annotation.NonNull; 8 | import android.support.design.widget.BottomSheetDialog; 9 | import android.support.design.widget.Snackbar; 10 | import android.support.v7.app.AlertDialog; 11 | import android.view.View; 12 | 13 | import permissions.dispatcher.PermissionRequest; 14 | 15 | /** 16 | * Created by f22labs on 18/03/17. 17 | */ 18 | 19 | public class Utils { 20 | 21 | 22 | public static boolean isDismissDialog(BottomSheetDialog dialog) { 23 | if (dialog != null && dialog.isShowing()) { 24 | dialog.dismiss(); 25 | return true; 26 | } 27 | 28 | return false; 29 | } 30 | 31 | 32 | public static final void showSnack(View view, String message) { 33 | 34 | Snackbar.make(view, message, Snackbar.LENGTH_SHORT).setAction("Done", new View.OnClickListener() { 35 | @Override 36 | public void onClick(View v) { 37 | 38 | 39 | } 40 | }).show(); 41 | } 42 | 43 | 44 | public static boolean isReadContactsPermissionEnabled(Context context) { 45 | 46 | PackageManager pm = context.getPackageManager(); 47 | int hasPerm = pm.checkPermission( 48 | Manifest.permission.READ_CONTACTS, 49 | context.getPackageName()); 50 | 51 | return (hasPerm == PackageManager.PERMISSION_GRANTED) ; 52 | 53 | } 54 | 55 | 56 | 57 | } 58 | -------------------------------------------------------------------------------- /app/src/main/java/com/takeoffandroid/shimmercontactsview/views/shimmer/ShimmerAdapter.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Copyright 2017 Harish Sridharan 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package com.takeoffandroid.shimmercontactsview.views.shimmer; 19 | 20 | import android.support.v7.widget.RecyclerView; 21 | import android.view.LayoutInflater; 22 | import android.view.ViewGroup; 23 | 24 | import com.takeoffandroid.shimmercontactsview.R; 25 | 26 | /** 27 | * Created by sharish on 22/11/16. 28 | */ 29 | 30 | public class ShimmerAdapter extends RecyclerView.Adapter { 31 | 32 | private int mItemCount = 10; 33 | private int mLayoutReference = R.layout.shimmer_sample_view; 34 | 35 | 36 | public void setMinItemCount(int itemCount) { 37 | mItemCount = itemCount; 38 | } 39 | 40 | @Override 41 | public ShimmerViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 42 | 43 | LayoutInflater inflater = LayoutInflater.from(parent.getContext()); 44 | return new ShimmerViewHolder(inflater, parent, mLayoutReference); 45 | } 46 | 47 | @Override 48 | public void onBindViewHolder(ShimmerViewHolder holder, int position) { 49 | holder.bind(); 50 | } 51 | 52 | @Override 53 | public int getItemCount() { 54 | return mItemCount; 55 | } 56 | 57 | public void setLayoutReference(int layoutReference) { 58 | this.mLayoutReference = layoutReference; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /app/src/main/java/com/takeoffandroid/shimmercontactsview/views/shimmer/ShimmerFrameLayout.java: -------------------------------------------------------------------------------- 1 | /** 2 | * For shimmer-android software 3 | * 4 | * Copyright (c) 2015, Facebook, Inc. All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 7 | * 8 | * * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 9 | * * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 10 | * 11 | * 12 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 13 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY 14 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 15 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 16 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 17 | */ 18 | 19 | package com.takeoffandroid.shimmercontactsview.views.shimmer; 20 | 21 | import android.animation.Animator; 22 | import android.animation.ObjectAnimator; 23 | import android.animation.ValueAnimator; 24 | import android.content.Context; 25 | import android.content.res.TypedArray; 26 | import android.graphics.Bitmap; 27 | import android.graphics.Canvas; 28 | import android.graphics.Color; 29 | import android.graphics.LinearGradient; 30 | import android.graphics.Paint; 31 | import android.graphics.PorterDuff; 32 | import android.graphics.PorterDuffXfermode; 33 | import android.graphics.RadialGradient; 34 | import android.graphics.Shader; 35 | import android.util.AttributeSet; 36 | import android.util.Log; 37 | import android.view.ViewTreeObserver; 38 | import android.widget.FrameLayout; 39 | 40 | import com.takeoffandroid.shimmercontactsview.R; 41 | 42 | 43 | public class ShimmerFrameLayout extends FrameLayout { 44 | 45 | private static final String TAG = "ShimmerFrameLayout"; 46 | private static final PorterDuffXfermode DST_IN_PORTER_DUFF_XFERMODE = new PorterDuffXfermode(PorterDuff.Mode.DST_IN); 47 | 48 | // enum specifying the shape of the highlight mask applied to the contained view 49 | public enum MaskShape { 50 | LINEAR, 51 | RADIAL 52 | } 53 | 54 | // enum controlling the angle of the highlight mask animation 55 | public enum MaskAngle { 56 | CW_0, // left to right 57 | CW_90, // top to bottom 58 | CW_180, // right to left 59 | CW_270, // bottom to top 60 | } 61 | 62 | // struct storing various mask related parameters, which are used to construct the mask bitmap 63 | private static class Mask { 64 | 65 | public MaskAngle angle; 66 | public float tilt; 67 | public float dropoff; 68 | public int fixedWidth; 69 | public int fixedHeight; 70 | public float intensity; 71 | public float relativeWidth; 72 | public float relativeHeight; 73 | public MaskShape shape; 74 | 75 | public int maskWidth(int width) { 76 | return fixedWidth > 0 ? fixedWidth : (int) (width * relativeWidth); 77 | } 78 | 79 | public int maskHeight(int height) { 80 | return fixedHeight > 0 ? fixedHeight : (int) (height * relativeHeight); 81 | } 82 | 83 | /** 84 | * Get the array of colors to be distributed along the gradient of the mask bitmap 85 | * 86 | * @return An array of black and transparent colors 87 | */ 88 | public int[] getGradientColors() { 89 | switch (shape) { 90 | default: 91 | case LINEAR: 92 | return new int[]{Color.TRANSPARENT, Color.BLACK, Color.BLACK, Color.TRANSPARENT}; 93 | case RADIAL: 94 | return new int[]{Color.BLACK, Color.BLACK, Color.TRANSPARENT}; 95 | } 96 | } 97 | 98 | /** 99 | * Get the array of relative positions [0..1] of each corresponding color in the colors array 100 | * 101 | * @return A array of float values in the [0..1] range 102 | */ 103 | public float[] getGradientPositions() { 104 | switch (shape) { 105 | default: 106 | case LINEAR: 107 | return new float[]{ 108 | Math.max((1.0f - intensity - dropoff) / 2, 0.0f), 109 | Math.max((1.0f - intensity) / 2, 0.0f), 110 | Math.min((1.0f + intensity) / 2, 1.0f), 111 | Math.min((1.0f + intensity + dropoff) / 2, 1.0f)}; 112 | case RADIAL: 113 | return new float[]{ 114 | 0.0f, 115 | Math.min(intensity, 1.0f), 116 | Math.min(intensity + dropoff, 1.0f)}; 117 | } 118 | } 119 | } 120 | 121 | // struct for storing the mask translation animation values 122 | private static class MaskTranslation { 123 | 124 | public int fromX; 125 | public int fromY; 126 | public int toX; 127 | public int toY; 128 | 129 | public void set(int fromX, int fromY, int toX, int toY) { 130 | this.fromX = fromX; 131 | this.fromY = fromY; 132 | this.toX = toX; 133 | this.toY = toY; 134 | } 135 | } 136 | 137 | private Paint mAlphaPaint; 138 | private Paint mMaskPaint; 139 | 140 | private Mask mMask; 141 | private MaskTranslation mMaskTranslation; 142 | 143 | private Bitmap mRenderMaskBitmap; 144 | private Bitmap mRenderUnmaskBitmap; 145 | 146 | private boolean mAutoStart; 147 | private int mDuration; 148 | private int mRepeatCount; 149 | private int mRepeatDelay; 150 | private int mRepeatMode; 151 | 152 | private int mMaskOffsetX; 153 | private int mMaskOffsetY; 154 | 155 | private boolean mAnimationStarted; 156 | private ViewTreeObserver.OnGlobalLayoutListener mGlobalLayoutListener; 157 | 158 | protected ValueAnimator mAnimator; 159 | protected Bitmap mMaskBitmap; 160 | 161 | public ShimmerFrameLayout(Context context) { 162 | this(context, null, 0); 163 | } 164 | 165 | public ShimmerFrameLayout(Context context, AttributeSet attrs) { 166 | this(context, attrs, 0); 167 | } 168 | 169 | public ShimmerFrameLayout(Context context, AttributeSet attrs, int defStyle) { 170 | super(context, attrs, defStyle); 171 | 172 | setWillNotDraw(false); 173 | 174 | mMask = new Mask(); 175 | mAlphaPaint = new Paint(); 176 | mMaskPaint = new Paint(); 177 | mMaskPaint.setAntiAlias(true); 178 | mMaskPaint.setDither(true); 179 | mMaskPaint.setFilterBitmap(true); 180 | mMaskPaint.setXfermode(DST_IN_PORTER_DUFF_XFERMODE); 181 | 182 | useDefaults(); 183 | 184 | if (attrs != null) { 185 | TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ShimmerFrameLayout, 0, 0); 186 | try { 187 | if (a.hasValue(R.styleable.ShimmerFrameLayout_auto_start)) { 188 | setAutoStart(a.getBoolean(R.styleable.ShimmerFrameLayout_auto_start, false)); 189 | } 190 | if (a.hasValue(R.styleable.ShimmerFrameLayout_base_alpha)) { 191 | setBaseAlpha(a.getFloat(R.styleable.ShimmerFrameLayout_base_alpha, 0)); 192 | } 193 | if (a.hasValue(R.styleable.ShimmerFrameLayout_duration)) { 194 | setDuration(a.getInt(R.styleable.ShimmerFrameLayout_duration, 0)); 195 | } 196 | if (a.hasValue(R.styleable.ShimmerFrameLayout_repeat_count)) { 197 | setRepeatCount(a.getInt(R.styleable.ShimmerFrameLayout_repeat_count, 0)); 198 | } 199 | if (a.hasValue(R.styleable.ShimmerFrameLayout_repeat_delay)) { 200 | setRepeatDelay(a.getInt(R.styleable.ShimmerFrameLayout_repeat_delay, 0)); 201 | } 202 | if (a.hasValue(R.styleable.ShimmerFrameLayout_repeat_mode)) { 203 | setRepeatMode(a.getInt(R.styleable.ShimmerFrameLayout_repeat_mode, 0)); 204 | } 205 | 206 | if (a.hasValue(R.styleable.ShimmerFrameLayout_angle)) { 207 | int angle = a.getInt(R.styleable.ShimmerFrameLayout_angle, 0); 208 | switch (angle) { 209 | default: 210 | case 0: 211 | mMask.angle = MaskAngle.CW_0; 212 | break; 213 | case 90: 214 | mMask.angle = MaskAngle.CW_90; 215 | break; 216 | case 180: 217 | mMask.angle = MaskAngle.CW_180; 218 | break; 219 | case 270: 220 | mMask.angle = MaskAngle.CW_270; 221 | break; 222 | } 223 | } 224 | 225 | if (a.hasValue(R.styleable.ShimmerFrameLayout_shape)) { 226 | int shape = a.getInt(R.styleable.ShimmerFrameLayout_shape, 0); 227 | switch (shape) { 228 | default: 229 | case 0: 230 | mMask.shape = MaskShape.LINEAR; 231 | break; 232 | case 2: 233 | mMask.shape = MaskShape.RADIAL; 234 | break; 235 | } 236 | } 237 | 238 | if (a.hasValue(R.styleable.ShimmerFrameLayout_dropoff)) { 239 | mMask.dropoff = a.getFloat(R.styleable.ShimmerFrameLayout_dropoff, 0); 240 | } 241 | if (a.hasValue(R.styleable.ShimmerFrameLayout_fixed_width)) { 242 | mMask.fixedWidth = a.getDimensionPixelSize(R.styleable.ShimmerFrameLayout_fixed_width, 0); 243 | } 244 | if (a.hasValue(R.styleable.ShimmerFrameLayout_fixed_height)) { 245 | mMask.fixedHeight = a.getDimensionPixelSize(R.styleable.ShimmerFrameLayout_fixed_height, 0); 246 | } 247 | if (a.hasValue(R.styleable.ShimmerFrameLayout_intensity)) { 248 | mMask.intensity = a.getFloat(R.styleable.ShimmerFrameLayout_intensity, 0); 249 | } 250 | if (a.hasValue(R.styleable.ShimmerFrameLayout_relative_width)) { 251 | mMask.relativeWidth = a.getFloat(R.styleable.ShimmerFrameLayout_relative_width, 0); 252 | } 253 | if (a.hasValue(R.styleable.ShimmerFrameLayout_relative_height)) { 254 | mMask.relativeHeight = a.getFloat(R.styleable.ShimmerFrameLayout_relative_height, 0); 255 | } 256 | if (a.hasValue(R.styleable.ShimmerFrameLayout_tilt)) { 257 | mMask.tilt = a.getFloat(R.styleable.ShimmerFrameLayout_tilt, 0); 258 | } 259 | } finally { 260 | a.recycle(); 261 | } 262 | } 263 | } 264 | 265 | /** 266 | * Resets the layout to its default state. Any parameters that were set or modified will be reverted back to their 267 | * original value. Also, stops the shimmer animation if it is currently playing. 268 | */ 269 | public void useDefaults() { 270 | // Set defaults 271 | setAutoStart(false); 272 | setDuration(1000); 273 | setRepeatCount(ObjectAnimator.INFINITE); 274 | setRepeatDelay(0); 275 | setRepeatMode(ObjectAnimator.RESTART); 276 | 277 | mMask.angle = MaskAngle.CW_0; 278 | mMask.shape = MaskShape.LINEAR; 279 | mMask.dropoff = 0.5f; 280 | mMask.fixedWidth = 0; 281 | mMask.fixedHeight = 0; 282 | mMask.intensity = 0.0f; 283 | mMask.relativeWidth = 1.0f; 284 | mMask.relativeHeight = 1.0f; 285 | mMask.tilt = 20; 286 | 287 | mMaskTranslation = new MaskTranslation(); 288 | 289 | setBaseAlpha(0.3f); 290 | 291 | resetAll(); 292 | } 293 | 294 | /** 295 | * Is 'auto start' enabled for this layout. When auto start is enabled, the layout will start animating automatically 296 | * whenever it is attached to the current window. 297 | * 298 | * @return True if 'auto start' is enabled, false otherwise 299 | */ 300 | public boolean isAutoStart() { 301 | return mAutoStart; 302 | } 303 | 304 | /** 305 | * Enable or disable 'auto start' for this layout. When auto start is enabled, the layout will start animating 306 | * automatically whenever it is attached to the current window. 307 | * 308 | * @param autoStart Whether auto start should be enabled or not 309 | */ 310 | public void setAutoStart(boolean autoStart) { 311 | mAutoStart = autoStart; 312 | resetAll(); 313 | } 314 | 315 | /** 316 | * Get the alpha currently used to render the base view i.e. the unhighlighted view over which the highlight is drawn. 317 | * 318 | * @return Alpha (opacity) of the base view 319 | */ 320 | public float getBaseAlpha() { 321 | return (float) mAlphaPaint.getAlpha() / 0xff; 322 | } 323 | 324 | /** 325 | * Set the alpha to be used to render the base view i.e. the unhighlighted view over which the highlight is drawn. 326 | * 327 | * @param alpha Alpha (opacity) of the base view 328 | */ 329 | public void setBaseAlpha(float alpha) { 330 | mAlphaPaint.setAlpha((int) (clamp(0, 1, alpha) * 0xff)); 331 | resetAll(); 332 | } 333 | 334 | /** 335 | * Get the duration of the current animation i.e. the time it takes for the highlight to move from one end 336 | * of the layout to the other. The default value is 1000 ms. 337 | * 338 | * @return Duration of the animation, in milliseconds 339 | */ 340 | public int getDuration() { 341 | return mDuration; 342 | } 343 | 344 | /** 345 | * Set the duration of the animation i.e. the time it will take for the highlight to move from one end of the layout 346 | * to the other. 347 | * 348 | * @param duration Duration of the animation, in milliseconds 349 | */ 350 | public void setDuration(int duration) { 351 | mDuration = duration; 352 | resetAll(); 353 | } 354 | 355 | /** 356 | * Get the number of times of the current animation will repeat. The default value is -1, which means the animation 357 | * will repeat indefinitely. 358 | * 359 | * @return Number of times the current animation will repeat, or -1 for indefinite. 360 | */ 361 | public int getRepeatCount() { 362 | return mRepeatCount; 363 | } 364 | 365 | /** 366 | * Set the number of times the animation should repeat. If the repeat count is 0, the animation stops after reaching 367 | * the end. If greater than 0, or -1 (for infinite), the repeat mode is taken into account. 368 | * 369 | * @param repeatCount Number of times the current animation should repeat, or -1 for indefinite. 370 | */ 371 | public void setRepeatCount(int repeatCount) { 372 | mRepeatCount = repeatCount; 373 | resetAll(); 374 | } 375 | 376 | /** 377 | * Get the delay after which the current animation will repeat. The default value is 0, which means the animation 378 | * will repeat immediately, unless it has ended. 379 | * 380 | * @return Delay after which the current animation will repeat, in milliseconds. 381 | */ 382 | public int getRepeatDelay() { 383 | return mRepeatDelay; 384 | } 385 | 386 | /** 387 | * Set the delay after which the animation repeat, unless it has ended. 388 | * 389 | * @param repeatDelay Delay after which the animation should repeat, in milliseconds. 390 | */ 391 | public void setRepeatDelay(int repeatDelay) { 392 | mRepeatDelay = repeatDelay; 393 | resetAll(); 394 | } 395 | 396 | /** 397 | * Get what the current animation will do after reaching the end. One of 398 | * REVERSE or 399 | * RESTART 400 | * 401 | * @return Repeat mode of the current animation 402 | */ 403 | public int getRepeatMode() { 404 | return mRepeatMode; 405 | } 406 | 407 | /** 408 | * Set what the animation should do after reaching the end. One of 409 | * REVERSE or 410 | * RESTART 411 | * 412 | * @param repeatMode Repeat mode of the animation 413 | */ 414 | public void setRepeatMode(int repeatMode) { 415 | mRepeatMode = repeatMode; 416 | resetAll(); 417 | } 418 | 419 | /** 420 | * Get the shape of the current animation's highlight mask. One of {@link MaskShape#LINEAR} or 421 | * {@link MaskShape#RADIAL} 422 | * 423 | * @return The shape of the highlight mask 424 | */ 425 | public MaskShape getMaskShape() { 426 | return mMask.shape; 427 | } 428 | 429 | /** 430 | * Set the shape of the animation's highlight mask. One of {@link MaskShape#LINEAR} or {@link MaskShape#RADIAL} 431 | * 432 | * @param shape The shape of the highlight mask 433 | */ 434 | public void setMaskShape(MaskShape shape) { 435 | mMask.shape = shape; 436 | resetAll(); 437 | } 438 | 439 | /** 440 | * Get the angle at which the highlight mask is animated. One of: 441 | *
    442 | *
  • {@link MaskAngle#CW_0} which animates left to right,
  • 443 | *
  • {@link MaskAngle#CW_90} which animates top to bottom,
  • 444 | *
  • {@link MaskAngle#CW_180} which animates right to left, or
  • 445 | *
  • {@link MaskAngle#CW_270} which animates bottom to top
  • 446 | *
447 | * 448 | * @return The {@link MaskAngle} of the current animation 449 | */ 450 | public MaskAngle getAngle() { 451 | return mMask.angle; 452 | } 453 | 454 | /** 455 | * Set the angle of the highlight mask animation. One of: 456 | *
    457 | *
  • {@link MaskAngle#CW_0} which animates left to right,
  • 458 | *
  • {@link MaskAngle#CW_90} which animates top to bottom,
  • 459 | *
  • {@link MaskAngle#CW_180} which animates right to left, or
  • 460 | *
  • {@link MaskAngle#CW_270} which animates bottom to top
  • 461 | *
462 | * 463 | * @param angle The {@link MaskAngle} of the new animation 464 | */ 465 | public void setAngle(MaskAngle angle) { 466 | mMask.angle = angle; 467 | resetAll(); 468 | } 469 | 470 | /** 471 | * Get the dropoff of the current animation's highlight mask. Dropoff controls the size of the fading edge of the 472 | * highlight. 473 | *

474 | * The default value of dropoff is 0.5. 475 | * 476 | * @return Dropoff of the highlight mask 477 | */ 478 | public float getDropoff() { 479 | return mMask.dropoff; 480 | } 481 | 482 | /** 483 | * Set the dropoff of the animation's highlight mask, which defines the size of the highlight's fading edge. 484 | *

485 | * It is the relative distance from the center at which the highlight mask's opacity is 0 i.e it is fully transparent. 486 | * For a linear mask, the distance is relative to the center towards the edges. For a radial mask, the distance is 487 | * relative to the center towards the circumference. So a dropoff of 0.5 on a linear mask will create a band that 488 | * is half the size of the corresponding edge (depending on the {@link MaskAngle}), centered in the layout. 489 | * 490 | * @param dropoff 491 | */ 492 | public void setDropoff(float dropoff) { 493 | mMask.dropoff = dropoff; 494 | resetAll(); 495 | } 496 | 497 | /** 498 | * Get the fixed width of the highlight mask, or 0 if it is not set. By default it is 0. 499 | * 500 | * @return The width of the highlight mask if set, in pixels. 501 | */ 502 | public int getFixedWidth() { 503 | return mMask.fixedWidth; 504 | } 505 | 506 | /** 507 | * Set the fixed width of the highlight mask, regardless of the size of the layout. 508 | * 509 | * @param fixedWidth The width of the highlight mask in pixels. 510 | */ 511 | public void setFixedWidth(int fixedWidth) { 512 | mMask.fixedWidth = fixedWidth; 513 | resetAll(); 514 | } 515 | 516 | /** 517 | * Get the fixed height of the highlight mask, or 0 if it is not set. By default it is 0. 518 | * 519 | * @return The height of the highlight mask if set, in pixels. 520 | */ 521 | public int getFixedHeight() { 522 | return mMask.fixedHeight; 523 | } 524 | 525 | /** 526 | * Set the fixed height of the highlight mask, regardless of the size of the layout. 527 | * 528 | * @param fixedHeight The height of the highlight mask in pixels. 529 | */ 530 | public void setFixedHeight(int fixedHeight) { 531 | mMask.fixedHeight = fixedHeight; 532 | resetAll(); 533 | } 534 | 535 | /** 536 | * Get the intensity of the highlight mask, in the [0..1] range. The intensity controls the brightness of the 537 | * highlight; the higher it is, the greater is the opaque region in the highlight. The default value is 0. 538 | * 539 | * @return The intensity of the highlight mask 540 | */ 541 | public float getIntensity() { 542 | return mMask.intensity; 543 | } 544 | 545 | /** 546 | * Set the intensity of the highlight mask, in the [0..1] range. 547 | *

548 | * Intensity is the point relative to the center where opacity starts dropping off, so an intensity of 0 would mean 549 | * that the highlight starts becoming translucent immediately from the center (the spread is controlled by 'dropoff'). 550 | * 551 | * @param intensity The intensity of the highlight mask. 552 | */ 553 | public void setIntensity(float intensity) { 554 | mMask.intensity = intensity; 555 | resetAll(); 556 | } 557 | 558 | /** 559 | * Get the width of the highlight mask relative to the layout's width. The default is 1.0, meaning that the mask is 560 | * of the same width as the layout. 561 | * 562 | * @return Relative width of the highlight mask. 563 | */ 564 | public float getRelativeWidth() { 565 | return mMask.relativeWidth; 566 | } 567 | 568 | /** 569 | * Set the width of the highlight mask relative to the layout's width, in the [0..1] range. 570 | * 571 | * @param relativeWidth Relative width of the highlight mask. 572 | */ 573 | public void setRelativeWidth(int relativeWidth) { 574 | mMask.relativeWidth = relativeWidth; 575 | resetAll(); 576 | } 577 | 578 | /** 579 | * Get the height of the highlight mask relative to the layout's height. The default is 1.0, meaning that the mask is 580 | * of the same height as the layout. 581 | * 582 | * @return Relative height of the highlight mask. 583 | */ 584 | public float getRelativeHeight() { 585 | return mMask.relativeHeight; 586 | } 587 | 588 | /** 589 | * Set the height of the highlight mask relative to the layout's height, in the [0..1] range. 590 | * 591 | * @param relativeHeight Relative height of the highlight mask. 592 | */ 593 | public void setRelativeHeight(int relativeHeight) { 594 | mMask.relativeHeight = relativeHeight; 595 | resetAll(); 596 | } 597 | 598 | /** 599 | * Get the tilt angle of the highlight, in degrees. The default value is 20. 600 | * 601 | * @return The highlight's tilt angle, in degrees. 602 | */ 603 | public float getTilt() { 604 | return mMask.tilt; 605 | } 606 | 607 | /** 608 | * Set the tile angle of the highlight, in degrees. 609 | * 610 | * @param tilt The highlight's tilt angle, in degrees. 611 | */ 612 | public void setTilt(float tilt) { 613 | mMask.tilt = tilt; 614 | resetAll(); 615 | } 616 | 617 | /** 618 | * Start the shimmer animation. If the 'auto start' property is set, this method is called automatically when the 619 | * layout is attached to the current window. Calling this method has no effect if the animation is already playing. 620 | */ 621 | public void startShimmerAnimation() { 622 | if (mAnimationStarted) { 623 | return; 624 | } 625 | Animator animator = getShimmerAnimation(); 626 | animator.start(); 627 | mAnimationStarted = true; 628 | } 629 | 630 | /** 631 | * Stop the shimmer animation. Calling this method has no effect if the animation hasn't been started yet. 632 | */ 633 | public void stopShimmerAnimation() { 634 | if (mAnimator != null) { 635 | mAnimator.end(); 636 | mAnimator.removeAllUpdateListeners(); 637 | mAnimator.cancel(); 638 | } 639 | mAnimator = null; 640 | mAnimationStarted = false; 641 | } 642 | 643 | /** 644 | * Whether the shimmer animation is currently underway. 645 | * 646 | * @return True if the shimmer animation is playing, false otherwise. 647 | */ 648 | public boolean isAnimationStarted() { 649 | return mAnimationStarted; 650 | } 651 | 652 | /** 653 | * Translate the mask offset horizontally. Used by the animator. 654 | * 655 | * @param maskOffsetX Horizontal translation offset of the mask 656 | */ 657 | private void setMaskOffsetX(int maskOffsetX) { 658 | if (mMaskOffsetX == maskOffsetX) { 659 | return; 660 | } 661 | mMaskOffsetX = maskOffsetX; 662 | invalidate(); 663 | } 664 | 665 | /** 666 | * Translate the mask offset vertically. Used by the animator. 667 | * 668 | * @param maskOffsetY Vertical translation offset of the mask 669 | */ 670 | private void setMaskOffsetY(int maskOffsetY) { 671 | if (mMaskOffsetY == maskOffsetY) { 672 | return; 673 | } 674 | mMaskOffsetY = maskOffsetY; 675 | invalidate(); 676 | } 677 | 678 | @Override 679 | protected void onAttachedToWindow() { 680 | super.onAttachedToWindow(); 681 | if (mGlobalLayoutListener == null) { 682 | mGlobalLayoutListener = getLayoutListener(); 683 | } 684 | getViewTreeObserver().addOnGlobalLayoutListener(mGlobalLayoutListener); 685 | } 686 | 687 | private ViewTreeObserver.OnGlobalLayoutListener getLayoutListener() { 688 | return new ViewTreeObserver.OnGlobalLayoutListener() { 689 | @Override 690 | public void onGlobalLayout() { 691 | boolean animationStarted = mAnimationStarted; 692 | resetAll(); 693 | if (mAutoStart || animationStarted) { 694 | startShimmerAnimation(); 695 | } 696 | } 697 | }; 698 | } 699 | 700 | @Override 701 | protected void onDetachedFromWindow() { 702 | stopShimmerAnimation(); 703 | if (mGlobalLayoutListener != null) { 704 | getViewTreeObserver().removeGlobalOnLayoutListener(mGlobalLayoutListener); 705 | mGlobalLayoutListener = null; 706 | } 707 | super.onDetachedFromWindow(); 708 | } 709 | 710 | @Override 711 | protected void dispatchDraw(Canvas canvas) { 712 | if (!mAnimationStarted || getWidth() <= 0 || getHeight() <= 0) { 713 | super.dispatchDraw(canvas); 714 | return; 715 | } 716 | dispatchDrawUsingBitmap(canvas); 717 | } 718 | 719 | private static float clamp(float min, float max, float value) { 720 | return Math.min(max, Math.max(min, value)); 721 | } 722 | 723 | /** 724 | * Draws and masks the children using a Bitmap. 725 | * 726 | * @param canvas Canvas that the masked children will end up being drawn to. 727 | */ 728 | private boolean dispatchDrawUsingBitmap(Canvas canvas) { 729 | Bitmap unmaskBitmap = tryObtainRenderUnmaskBitmap(); 730 | Bitmap maskBitmap = tryObtainRenderMaskBitmap(); 731 | if (unmaskBitmap == null || maskBitmap == null) { 732 | return false; 733 | } 734 | // First draw a desaturated version 735 | drawUnmasked(new Canvas(unmaskBitmap)); 736 | canvas.drawBitmap(unmaskBitmap, 0, 0, mAlphaPaint); 737 | 738 | // Then draw the masked version 739 | drawMasked(new Canvas(maskBitmap)); 740 | canvas.drawBitmap(maskBitmap, 0, 0, null); 741 | 742 | return true; 743 | } 744 | 745 | private Bitmap tryObtainRenderUnmaskBitmap() { 746 | if (mRenderUnmaskBitmap == null) { 747 | mRenderUnmaskBitmap = tryCreateRenderBitmap(); 748 | } 749 | return mRenderUnmaskBitmap; 750 | } 751 | 752 | private Bitmap tryObtainRenderMaskBitmap() { 753 | if (mRenderMaskBitmap == null) { 754 | mRenderMaskBitmap = tryCreateRenderBitmap(); 755 | } 756 | return mRenderMaskBitmap; 757 | } 758 | 759 | private Bitmap tryCreateRenderBitmap() { 760 | int width = getWidth(); 761 | int height = getHeight(); 762 | try { 763 | return createBitmapAndGcIfNecessary(width, height); 764 | } catch (OutOfMemoryError e) { 765 | String logMessage = "ShimmerFrameLayout failed to create working bitmap"; 766 | StringBuilder logMessageStringBuilder = new StringBuilder(logMessage); 767 | logMessageStringBuilder.append(" (width = "); 768 | logMessageStringBuilder.append(width); 769 | logMessageStringBuilder.append(", height = "); 770 | logMessageStringBuilder.append(height); 771 | logMessageStringBuilder.append(")\n\n"); 772 | for (StackTraceElement stackTraceElement : 773 | Thread.currentThread().getStackTrace()) { 774 | logMessageStringBuilder.append(stackTraceElement.toString()); 775 | logMessageStringBuilder.append("\n"); 776 | } 777 | logMessage = logMessageStringBuilder.toString(); 778 | Log.d(TAG, logMessage); 779 | } 780 | return null; 781 | } 782 | 783 | // Draws the children without any mask. 784 | private void drawUnmasked(Canvas renderCanvas) { 785 | renderCanvas.drawColor(0, PorterDuff.Mode.CLEAR); 786 | super.dispatchDraw(renderCanvas); 787 | } 788 | 789 | // Draws the children and masks them on the given Canvas. 790 | private void drawMasked(Canvas renderCanvas) { 791 | Bitmap maskBitmap = getMaskBitmap(); 792 | if (maskBitmap == null) { 793 | return; 794 | } 795 | 796 | renderCanvas.clipRect( 797 | mMaskOffsetX, 798 | mMaskOffsetY, 799 | mMaskOffsetX + maskBitmap.getWidth(), 800 | mMaskOffsetY + maskBitmap.getHeight()); 801 | renderCanvas.drawColor(0, PorterDuff.Mode.CLEAR); 802 | super.dispatchDraw(renderCanvas); 803 | 804 | renderCanvas.drawBitmap(maskBitmap, mMaskOffsetX, mMaskOffsetY, mMaskPaint); 805 | } 806 | 807 | private void resetAll() { 808 | stopShimmerAnimation(); 809 | resetMaskBitmap(); 810 | resetRenderedView(); 811 | } 812 | 813 | // If a mask bitmap was created, it's recycled and set to null so it will be recreated when needed. 814 | private void resetMaskBitmap() { 815 | if (mMaskBitmap != null) { 816 | mMaskBitmap.recycle(); 817 | mMaskBitmap = null; 818 | } 819 | } 820 | 821 | // If a working bitmap was created, it's recycled and set to null so it will be recreated when needed. 822 | private void resetRenderedView() { 823 | if (mRenderUnmaskBitmap != null) { 824 | mRenderUnmaskBitmap.recycle(); 825 | mRenderUnmaskBitmap = null; 826 | } 827 | 828 | if (mRenderMaskBitmap != null) { 829 | mRenderMaskBitmap.recycle(); 830 | mRenderMaskBitmap = null; 831 | } 832 | } 833 | 834 | // Return the mask bitmap, creating it if necessary. 835 | private Bitmap getMaskBitmap() { 836 | if (mMaskBitmap != null) { 837 | return mMaskBitmap; 838 | } 839 | 840 | int width = mMask.maskWidth(getWidth()); 841 | int height = mMask.maskHeight(getHeight()); 842 | 843 | mMaskBitmap = createBitmapAndGcIfNecessary(width, height); 844 | Canvas canvas = new Canvas(mMaskBitmap); 845 | Shader gradient; 846 | switch (mMask.shape) { 847 | default: 848 | case LINEAR: { 849 | int x1, y1; 850 | int x2, y2; 851 | switch (mMask.angle) { 852 | default: 853 | case CW_0: 854 | x1 = 0; 855 | y1 = 0; 856 | x2 = width; 857 | y2 = 0; 858 | break; 859 | case CW_90: 860 | x1 = 0; 861 | y1 = 0; 862 | x2 = 0; 863 | y2 = height; 864 | break; 865 | case CW_180: 866 | x1 = width; 867 | y1 = 0; 868 | x2 = 0; 869 | y2 = 0; 870 | break; 871 | case CW_270: 872 | x1 = 0; 873 | y1 = height; 874 | x2 = 0; 875 | y2 = 0; 876 | break; 877 | } 878 | gradient = 879 | new LinearGradient( 880 | x1, y1, 881 | x2, y2, 882 | mMask.getGradientColors(), 883 | mMask.getGradientPositions(), 884 | Shader.TileMode.REPEAT); 885 | break; 886 | } 887 | case RADIAL: { 888 | int x = width / 2; 889 | int y = height / 2; 890 | gradient = 891 | new RadialGradient( 892 | x, 893 | y, 894 | (float) (Math.max(width, height) / Math.sqrt(2)), 895 | mMask.getGradientColors(), 896 | mMask.getGradientPositions(), 897 | Shader.TileMode.REPEAT); 898 | break; 899 | } 900 | } 901 | canvas.rotate(mMask.tilt, width / 2, height / 2); 902 | Paint paint = new Paint(); 903 | paint.setShader(gradient); 904 | // We need to increase the rect size to account for the tilt 905 | int padding = (int) (Math.sqrt(2) * Math.max(width, height)) / 2; 906 | canvas.drawRect(-padding, -padding, width + padding, height + padding, paint); 907 | 908 | return mMaskBitmap; 909 | } 910 | 911 | // Get the shimmer Animator 912 | // object, which is responsible for driving the highlight mask animation. 913 | private Animator getShimmerAnimation() { 914 | if (mAnimator != null) { 915 | return mAnimator; 916 | } 917 | int width = getWidth(); 918 | int height = getHeight(); 919 | switch (mMask.shape) { 920 | default: 921 | case LINEAR: 922 | switch (mMask.angle) { 923 | default: 924 | case CW_0: 925 | mMaskTranslation.set(-width, 0, width, 0); 926 | break; 927 | case CW_90: 928 | mMaskTranslation.set(0, -height, 0, height); 929 | break; 930 | case CW_180: 931 | mMaskTranslation.set(width, 0, -width, 0); 932 | break; 933 | case CW_270: 934 | mMaskTranslation.set(0, height, 0, -height); 935 | break; 936 | } 937 | } 938 | mAnimator = ValueAnimator.ofFloat(0.0f, 1.0f + (float) mRepeatDelay / mDuration); 939 | mAnimator.setDuration(mDuration + mRepeatDelay); 940 | mAnimator.setRepeatCount(mRepeatCount); 941 | mAnimator.setRepeatMode(mRepeatMode); 942 | mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 943 | @Override 944 | public void onAnimationUpdate(ValueAnimator animation) { 945 | float value = Math.max(0.0f, Math.min(1.0f, (Float) animation.getAnimatedValue())); 946 | setMaskOffsetX((int) (mMaskTranslation.fromX * (1 - value) + mMaskTranslation.toX * value)); 947 | setMaskOffsetY((int) (mMaskTranslation.fromY * (1 - value) + mMaskTranslation.toY * value)); 948 | } 949 | }); 950 | return mAnimator; 951 | } 952 | 953 | /** 954 | * Creates a bitmap with the given width and height. 955 | *

956 | * If it fails with an OutOfMemory error, it will force a GC and then try to create the bitmap 957 | * one more time. 958 | * 959 | * @param width width of the bitmap 960 | * @param height height of the bitmap 961 | */ 962 | protected static Bitmap createBitmapAndGcIfNecessary(int width, int height) { 963 | try { 964 | return Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 965 | } catch (OutOfMemoryError e) { 966 | System.gc(); 967 | return Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 968 | } 969 | } 970 | } -------------------------------------------------------------------------------- /app/src/main/java/com/takeoffandroid/shimmercontactsview/views/shimmer/ShimmerRecyclerView.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Harish Sridharan 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.takeoffandroid.shimmercontactsview.views.shimmer; 18 | 19 | import android.content.Context; 20 | import android.content.res.TypedArray; 21 | import android.support.annotation.Nullable; 22 | import android.support.v7.widget.GridLayoutManager; 23 | import android.support.v7.widget.LinearLayoutManager; 24 | import android.support.v7.widget.RecyclerView; 25 | import android.util.AttributeSet; 26 | 27 | import com.takeoffandroid.shimmercontactsview.R; 28 | 29 | 30 | /** 31 | * Created by sharish on 22/11/16. 32 | */ 33 | 34 | public class ShimmerRecyclerView extends RecyclerView { 35 | 36 | public enum LayoutMangerType { 37 | LINEAR_VERTICAL, LINEAR_HORIZONTAL, GRID 38 | } 39 | 40 | private ShimmerAdapter mShimmerAdapter; 41 | private LayoutManager mShimmerLayoutManager; 42 | 43 | private LayoutManager mActualLayoutManager; 44 | private Adapter mActualAdapter; 45 | 46 | 47 | private int mLayoutReference = R.layout.shimmer_sample_view; 48 | private boolean mCanScroll; 49 | private LayoutMangerType mLayoutMangerType = LayoutMangerType.LINEAR_VERTICAL; 50 | private int mGridCount = 2; 51 | 52 | public ShimmerRecyclerView(Context context) { 53 | super(context); 54 | init(null); 55 | } 56 | 57 | public ShimmerRecyclerView(Context context, @Nullable AttributeSet attrs) { 58 | super(context, attrs); 59 | init(attrs); 60 | } 61 | 62 | public ShimmerRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) { 63 | super(context, attrs, defStyle); 64 | init(attrs); 65 | } 66 | 67 | private void init(AttributeSet attrs) { 68 | 69 | mShimmerAdapter = new ShimmerAdapter(); 70 | TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.ShimmerRecyclerView, 0, 0); 71 | try { 72 | if (a.hasValue(R.styleable.ShimmerRecyclerView_demo_layout)) { 73 | setDemoLayoutReference(a.getResourceId(R.styleable.ShimmerRecyclerView_demo_layout, R.layout.shimmer_sample_view)); 74 | } 75 | 76 | if (a.hasValue(R.styleable.ShimmerRecyclerView_demo_child_count)) { 77 | setDemoChildCount(a.getInteger(R.styleable.ShimmerRecyclerView_demo_child_count, 1)); 78 | } 79 | 80 | if (a.hasValue(R.styleable.ShimmerRecyclerView_demo_layout_manager_type)) { 81 | int value = a.getInteger(R.styleable.ShimmerRecyclerView_demo_layout_manager_type, 0); 82 | switch (value) { 83 | case 1: 84 | setDemoLayoutManager(LayoutMangerType.LINEAR_HORIZONTAL); 85 | break; 86 | case 2: 87 | setDemoLayoutManager(LayoutMangerType.GRID); 88 | break; 89 | case 0: 90 | default: 91 | setDemoLayoutManager(LayoutMangerType.LINEAR_VERTICAL); 92 | break; 93 | 94 | } 95 | } 96 | 97 | if (a.hasValue(R.styleable.ShimmerRecyclerView_demo_grid_child_count)) { 98 | setGridChildCount(a.getInteger(R.styleable.ShimmerRecyclerView_demo_grid_child_count, 2)); 99 | } 100 | 101 | } finally { 102 | a.recycle(); 103 | } 104 | 105 | showShimmerAdapter(); 106 | 107 | 108 | } 109 | 110 | /** 111 | * Specifies the number of child should exist in any row of the grid layout. 112 | * 113 | * @param count - count specifying the number of child. 114 | */ 115 | public void setGridChildCount(int count) { 116 | mGridCount = count; 117 | } 118 | 119 | /** 120 | * Sets the layout manager for the shimmer adapter. 121 | * 122 | * @param type layout manager reference 123 | */ 124 | public void setDemoLayoutManager(LayoutMangerType type) { 125 | mLayoutMangerType = type; 126 | 127 | } 128 | 129 | /** 130 | * Sets the number of demo views should be shown in the shimmer adapter. 131 | * 132 | * @param count - number of demo views should be shown. 133 | */ 134 | public void setDemoChildCount(int count) { 135 | mShimmerAdapter.setMinItemCount(count); 136 | } 137 | 138 | /** 139 | * Sets the shimmer adapter and shows the loading screen. 140 | */ 141 | public void showShimmerAdapter() { 142 | mCanScroll = false; 143 | 144 | if (mShimmerLayoutManager == null) { 145 | initShimmerManager(); 146 | } 147 | 148 | setLayoutManager(mShimmerLayoutManager); 149 | setAdapter(mShimmerAdapter); 150 | 151 | } 152 | 153 | private void initShimmerManager() { 154 | 155 | switch (mLayoutMangerType) { 156 | case LINEAR_VERTICAL: 157 | mShimmerLayoutManager = new LinearLayoutManager(getContext()) { 158 | public boolean canScrollVertically() { 159 | return mCanScroll; 160 | } 161 | }; 162 | break; 163 | case LINEAR_HORIZONTAL: 164 | mShimmerLayoutManager = new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, false) { 165 | public boolean canScrollHorizontally() { 166 | return mCanScroll; 167 | } 168 | }; 169 | break; 170 | case GRID: 171 | mShimmerLayoutManager = new GridLayoutManager(getContext(), mGridCount) { 172 | public boolean canScrollVertically() { 173 | return mCanScroll; 174 | } 175 | }; 176 | break; 177 | 178 | 179 | } 180 | } 181 | 182 | /** 183 | * Hides the shimmer adapter 184 | */ 185 | public void hideShimmerAdapter() { 186 | mCanScroll = true; 187 | setLayoutManager(mActualLayoutManager); 188 | setAdapter(mActualAdapter); 189 | } 190 | 191 | 192 | public void setLayoutManager(LayoutManager manager) { 193 | 194 | if (manager == null) { 195 | mActualLayoutManager = null; 196 | } else if (manager != mShimmerLayoutManager) { 197 | mActualLayoutManager = manager; 198 | } 199 | 200 | super.setLayoutManager(manager); 201 | } 202 | 203 | 204 | public void setAdapter(Adapter adapter) { 205 | 206 | if (adapter == null) { 207 | mActualAdapter = null; 208 | } else if (adapter != mShimmerAdapter) { 209 | mActualAdapter = adapter; 210 | } 211 | 212 | super.setAdapter(adapter); 213 | 214 | } 215 | 216 | 217 | public int getLayoutReference() { 218 | return mLayoutReference; 219 | } 220 | 221 | /** 222 | * Sets the demo layout reference 223 | * 224 | * @param mLayoutReference layout resource id of the layout which should be shown as demo. 225 | */ 226 | public void setDemoLayoutReference(int mLayoutReference) { 227 | this.mLayoutReference = mLayoutReference; 228 | mShimmerAdapter.setLayoutReference(getLayoutReference()); 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /app/src/main/java/com/takeoffandroid/shimmercontactsview/views/shimmer/ShimmerViewHolder.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Copyright 2017 Harish Sridharan 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package com.takeoffandroid.shimmercontactsview.views.shimmer; 19 | 20 | import android.support.v7.widget.RecyclerView; 21 | import android.view.LayoutInflater; 22 | import android.view.View; 23 | import android.view.ViewGroup; 24 | 25 | import com.takeoffandroid.shimmercontactsview.R; 26 | 27 | 28 | /** 29 | * Created by sharish on 22/11/16. 30 | */ 31 | 32 | public class ShimmerViewHolder extends RecyclerView.ViewHolder { 33 | 34 | public ShimmerViewHolder(LayoutInflater inflater, ViewGroup parent, int innerViewResId) { 35 | super(inflater.inflate(R.layout.lib_shimmer_viewholder, parent, false)); 36 | ShimmerFrameLayout layout = (ShimmerFrameLayout) itemView; 37 | 38 | View innerView = inflater.inflate(innerViewResId, layout, false); 39 | layout.addView(innerView); 40 | layout.setAutoStart(false); 41 | } 42 | 43 | /** 44 | * Binds the view 45 | */ 46 | public void bind() { 47 | 48 | ShimmerFrameLayout layout = (ShimmerFrameLayout) itemView; 49 | layout.startShimmerAnimation(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/src/main/java/com/takeoffandroid/shimmercontactsview/views/typefacehelper/DialogUtils.java: -------------------------------------------------------------------------------- 1 | package com.takeoffandroid.shimmercontactsview.views.typefacehelper; 2 | 3 | import android.app.AlertDialog; 4 | import android.app.Dialog; 5 | import android.app.ProgressDialog; 6 | import android.content.DialogInterface; 7 | import android.widget.Button; 8 | import android.widget.TextView; 9 | 10 | final class DialogUtils { 11 | public static void setTypeface(TypefaceHelper helper, D dialog, String typefaceName, int style) { 12 | if (dialog instanceof ProgressDialog) { 13 | setTypeface(helper, (ProgressDialog) dialog, typefaceName, style); 14 | } else if (dialog instanceof AlertDialog) { 15 | setTypeface(helper, (AlertDialog) dialog, typefaceName, style); 16 | } 17 | } 18 | 19 | private static void setTypeface(TypefaceHelper helper, AlertDialog alertDialog, String typefaceName, int style) { 20 | Button positive = alertDialog.getButton(DialogInterface.BUTTON_POSITIVE); 21 | Button negative = alertDialog.getButton(DialogInterface.BUTTON_NEGATIVE); 22 | Button neutral = alertDialog.getButton(DialogInterface.BUTTON_NEUTRAL); 23 | TextView message = (TextView) alertDialog.findViewById(android.R.id.message); 24 | if (positive != null) { 25 | helper.setTypeface(positive, typefaceName, style); 26 | } 27 | if (negative != null) { 28 | helper.setTypeface(negative, typefaceName, style); 29 | } 30 | if (neutral != null) { 31 | helper.setTypeface(neutral, typefaceName, style); 32 | } 33 | if (message != null) { 34 | helper.setTypeface(message, typefaceName, style); 35 | } 36 | } 37 | 38 | private static void setTypeface(TypefaceHelper helper, ProgressDialog progressDialog, String typefaceName, int style) { 39 | TextView message = (TextView) progressDialog.findViewById(android.R.id.message); 40 | if (message != null) { 41 | helper.setTypeface(message, typefaceName, style); 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /app/src/main/java/com/takeoffandroid/shimmercontactsview/views/typefacehelper/TypefaceCache.java: -------------------------------------------------------------------------------- 1 | package com.takeoffandroid.shimmercontactsview.views.typefacehelper; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | import android.graphics.Typeface; 6 | 7 | import java.util.Hashtable; 8 | 9 | /** 10 | * This is a typeface instance cache. 11 | * The cache is to avoid memory leak problem when a typeface is loaded. 12 | * See the link for more details about the memory leak issue. 13 | * (https://code.google.com/p/android/issues/detail?id=9904) 14 | * 15 | * @author hnakagawa 16 | */ 17 | /* package */ final class TypefaceCache { 18 | private static TypefaceCache sInstance; 19 | 20 | private final Hashtable mCache = new Hashtable(); 21 | 22 | private final Application mApplication; 23 | 24 | private TypefaceCache(Application application) { 25 | mApplication = application; 26 | } 27 | 28 | /** 29 | * If the cache has an instance for the typeface name, this will return the instance immediately. 30 | * Otherwise this method will create typeface instance and put it into the cache and return the instance. 31 | * @param name the typeface name. 32 | * @return {@link android.graphics.Typeface} instance. 33 | */ 34 | public synchronized Typeface get(String name) { 35 | Typeface typeface = mCache.get(name); 36 | if(typeface == null) { 37 | try { 38 | typeface = Typeface.createFromAsset(mApplication.getAssets(), name); 39 | } catch (Exception exp) { 40 | return null; 41 | } 42 | mCache.put(name, typeface); 43 | } 44 | return typeface; 45 | } 46 | 47 | /** 48 | * Retrieve this cache. 49 | * @param context the context. 50 | * @return the cache instance. 51 | */ 52 | public static synchronized TypefaceCache getInstance(Context context) { 53 | if (sInstance == null) 54 | sInstance = new TypefaceCache((Application)context.getApplicationContext()); 55 | return sInstance; 56 | } 57 | } -------------------------------------------------------------------------------- /app/src/main/java/com/takeoffandroid/shimmercontactsview/views/typefacehelper/TypefaceHelper.java: -------------------------------------------------------------------------------- 1 | package com.takeoffandroid.shimmercontactsview.views.typefacehelper; 2 | 3 | import android.annotation.TargetApi; 4 | import android.app.Activity; 5 | import android.app.Application; 6 | import android.app.Dialog; 7 | import android.content.Context; 8 | import android.graphics.Paint; 9 | import android.graphics.Typeface; 10 | import android.os.Build; 11 | import android.support.annotation.LayoutRes; 12 | import android.support.annotation.StringRes; 13 | import android.util.Log; 14 | import android.view.LayoutInflater; 15 | import android.view.View; 16 | import android.view.ViewGroup; 17 | import android.widget.TextView; 18 | import android.widget.Toast; 19 | 20 | /** 21 | * Helper class for setting typeface to the text views. 22 | * @author KeithYokoma 23 | */ 24 | @SuppressWarnings("unused") // public APIs 25 | public final class TypefaceHelper { 26 | public static final String TAG = TypefaceHelper.class.getSimpleName(); 27 | private static TypefaceHelper sHelper; 28 | private final Application mApplication; 29 | private final TypefaceCache mCache; 30 | 31 | private TypefaceHelper(Application application) { 32 | mApplication = application; 33 | mCache = TypefaceCache.getInstance(application); 34 | } 35 | 36 | /** 37 | * Initialize the instance. 38 | * @param application the application context. 39 | */ 40 | public static synchronized void initialize(Application application) { 41 | if (sHelper != null) { 42 | Log.v(TAG, "already initialized"); 43 | } 44 | sHelper = new TypefaceHelper(application); 45 | } 46 | 47 | /** 48 | * Terminate the instance. 49 | */ 50 | public static synchronized void destroy() { 51 | if (sHelper == null) { 52 | Log.v(TAG, "not initialized yet"); 53 | return; 54 | } 55 | sHelper = null; 56 | } 57 | 58 | /** 59 | * Retrieve the helper instance. 60 | * @return the helper instance. 61 | */ 62 | public static synchronized TypefaceHelper getInstance() { 63 | if (sHelper == null) { 64 | throw new IllegalArgumentException("Instance is not initialized yet. Call initialize() first."); 65 | } 66 | return sHelper; 67 | } 68 | 69 | /** 70 | * Fetches font instance from TypefaceCache 71 | * @param typefaceName typeface name. 72 | * @return requested typeface 73 | */ 74 | public Typeface getTypeface(String typefaceName){ 75 | return mCache.get(typefaceName); 76 | } 77 | 78 | /** 79 | * Set the typeface to the target view. 80 | * @param view to set typeface. 81 | * @param typefaceName typeface name. 82 | * @param text view parameter. 83 | */ 84 | public void setTypeface(V view, String typefaceName) { 85 | Typeface typeface = mCache.get(typefaceName); 86 | view.setTypeface(typeface); 87 | } 88 | 89 | /** 90 | * Set the typeface to the target view. 91 | * @param view to set typeface. 92 | * @param strResId string resource containing typeface name. 93 | * @param text view parameter. 94 | */ 95 | public void setTypeface(V view, @StringRes int strResId) { 96 | setTypeface(view, mApplication.getString(strResId)); 97 | } 98 | 99 | /** 100 | * Set the typeface to the target view. 101 | * @param view to set typeface. 102 | * @param typefaceName typeface name. 103 | * @param style the typeface style. 104 | * @param text view parameter. 105 | */ 106 | public void setTypeface(V view, String typefaceName, int style) { 107 | Typeface typeface = mCache.get(typefaceName); 108 | view.setTypeface(typeface, style); 109 | } 110 | 111 | /** 112 | * Set the typeface to the target view. 113 | * @param view to set typeface. 114 | * @param strResId string resource containing typeface name. 115 | * @param style the typeface style. 116 | * @param text view parameter. 117 | */ 118 | public void setTypeface(V view, @StringRes int strResId, int style) { 119 | setTypeface(view, mApplication.getString(strResId), style); 120 | } 121 | 122 | /** 123 | * Set the typeface to the all text views belong to the view group. 124 | * Note that this method recursively trace the child view groups and set typeface for the text views. 125 | * @param viewGroup the view group that contains text views. 126 | * @param typefaceName typeface name. 127 | * @param view group parameter. 128 | */ 129 | public void setTypeface(V viewGroup, String typefaceName) { 130 | int count = viewGroup.getChildCount(); 131 | for (int i = 0; i < count; i++) { 132 | View child = viewGroup.getChildAt(i); 133 | if (child instanceof ViewGroup) { 134 | setTypeface((ViewGroup) child, typefaceName); 135 | continue; 136 | } 137 | if (!(child instanceof TextView)) { 138 | continue; 139 | } 140 | setTypeface((TextView) child, typefaceName); 141 | } 142 | } 143 | 144 | /** 145 | * Set the typeface to the all text views belong to the view group. 146 | * Note that this method recursively trace the child view groups and set typeface for the text views. 147 | * @param viewGroup the view group that contains text views. 148 | * @param strResId string resource containing typeface name. 149 | * @param view group parameter. 150 | */ 151 | public void setTypeface(V viewGroup, @StringRes int strResId) { 152 | setTypeface(viewGroup, mApplication.getString(strResId)); 153 | } 154 | 155 | /** 156 | * Set the typeface to the all text views belong to the view group. 157 | * Note that this method recursively trace the child view groups and set typeface for the text views. 158 | * @param viewGroup the view group that contains text views. 159 | * @param typefaceName typeface name. 160 | * @param style the typeface style. 161 | * @param view group parameter. 162 | */ 163 | public void setTypeface(V viewGroup, String typefaceName, int style) { 164 | int count = viewGroup.getChildCount(); 165 | for (int i = 0; i < count; i++) { 166 | View child = viewGroup.getChildAt(i); 167 | if (child instanceof ViewGroup) { 168 | setTypeface((ViewGroup) child, typefaceName, style); 169 | continue; 170 | } 171 | if (!(child instanceof TextView)) { 172 | continue; 173 | } 174 | setTypeface((TextView) child, typefaceName, style); 175 | } 176 | } 177 | 178 | /** 179 | * Set the typeface to the all text views belong to the view group. 180 | * Note that this method recursively trace the child view groups and set typeface for the text views. 181 | * @param viewGroup the view group that contains text views. 182 | * @param strResId string resource containing typeface name. 183 | * @param style the typeface style. 184 | * @param view group parameter. 185 | */ 186 | public void setTypeface(V viewGroup, @StringRes int strResId, int style) { 187 | setTypeface(viewGroup, mApplication.getString(strResId), style); 188 | } 189 | 190 | /** 191 | * Set the typeface to the target paint. 192 | * @param paint the set typeface. 193 | * @param typefaceName typeface name. 194 | */ 195 | public void setTypeface(Paint paint, String typefaceName) { 196 | Typeface typeface = mCache.get(typefaceName); 197 | paint.setTypeface(typeface); 198 | } 199 | 200 | /** 201 | * Set the typeface to the target paint. 202 | * @param paint the set typeface. 203 | * @param strResId string resource containing typeface name. 204 | */ 205 | public void setTypeface(Paint paint, @StringRes int strResId) { 206 | setTypeface(paint, mApplication.getString(strResId)); 207 | } 208 | 209 | /** 210 | * Set the typeface to the all text views belong to the view group. 211 | * @param context the context. 212 | * @param layoutRes the layout resource id. 213 | * @param typefaceName typeface name. 214 | * @return the view. 215 | */ 216 | public View setTypeface(Context context, @LayoutRes int layoutRes, String typefaceName) { 217 | return setTypeface(context, layoutRes, null, typefaceName); 218 | } 219 | 220 | /** 221 | * Set the typeface to the all text views belong to the view group. 222 | * @param context the context. 223 | * @param layoutRes the layout resource id. 224 | * @param strResId string resource containing typeface name. 225 | * @return the view. 226 | */ 227 | public View setTypeface(Context context, @LayoutRes int layoutRes, @StringRes int strResId) { 228 | return setTypeface(context, layoutRes, mApplication.getString(strResId)); 229 | } 230 | 231 | /** 232 | * Set the typeface to the all text views belong to the view group. 233 | * @param context the context. 234 | * @param layoutRes the layout resource id. 235 | * @param parent the parent view group to attach the layout. 236 | * @param typefaceName typeface name. 237 | * @return the view. 238 | */ 239 | public View setTypeface(Context context, @LayoutRes int layoutRes, ViewGroup parent, String typefaceName) { 240 | ViewGroup view = (ViewGroup) LayoutInflater.from(context).inflate(layoutRes, parent); 241 | setTypeface(view, typefaceName); 242 | return view; 243 | } 244 | 245 | /** 246 | * Set the typeface to the all text views belong to the view group. 247 | * @param context the context. 248 | * @param layoutRes the layout resource id. 249 | * @param parent the parent view group to attach the layout. 250 | * @param strResId string resource containing typeface name. 251 | * @return the view. 252 | */ 253 | public View setTypeface(Context context, @LayoutRes int layoutRes, ViewGroup parent, @StringRes int strResId) { 254 | return setTypeface(context, layoutRes, parent, mApplication.getString(strResId)); 255 | } 256 | 257 | /** 258 | * Set the typeface to the all text views belong to the view group. 259 | * @param context the context. 260 | * @param layoutRes the layout resource id. 261 | * @param typefaceName typeface name. 262 | * @param style the typeface style. 263 | * @return the view. 264 | */ 265 | public View setTypeface(Context context, @LayoutRes int layoutRes, String typefaceName, int style) { 266 | return setTypeface(context, layoutRes, null, typefaceName, 0); 267 | } 268 | 269 | /** 270 | * Set the typeface to the all text views belong to the view group. 271 | * @param context the context. 272 | * @param layoutRes the layout resource id. 273 | * @param strResId string resource containing typeface name. 274 | * @param style the typeface style. 275 | * @return the view. 276 | */ 277 | public View setTypeface(Context context, @LayoutRes int layoutRes, @StringRes int strResId, int style) { 278 | return setTypeface(context, layoutRes, mApplication.getString(strResId), 0); 279 | } 280 | 281 | /** 282 | * Set the typeface to the all text views belong to the view group. 283 | * @param context the context. 284 | * @param layoutRes the layout resource id. 285 | * @param parent the parent view group to attach the layout. 286 | * @param typefaceName typeface name. 287 | * @param style the typeface style. 288 | * @return the view. 289 | */ 290 | public View setTypeface(Context context, @LayoutRes int layoutRes, ViewGroup parent, String typefaceName, int style) { 291 | ViewGroup view = (ViewGroup) LayoutInflater.from(context).inflate(layoutRes, parent); 292 | setTypeface(view, typefaceName, style); 293 | return view; 294 | } 295 | 296 | /** 297 | * Set the typeface to the all text views belong to the view group. 298 | * @param context the context. 299 | * @param layoutRes the layout resource id. 300 | * @param parent the parent view group to attach the layout. 301 | * @param strResId string resource containing typeface name. 302 | * @param style the typeface style. 303 | * @return the view. 304 | */ 305 | public View setTypeface(Context context, @LayoutRes int layoutRes, ViewGroup parent, @StringRes int strResId, int style) { 306 | return setTypeface(context, layoutRes, parent, mApplication.getString(strResId), style); 307 | } 308 | 309 | /** 310 | * Set the typeface to the all text views belong to the activity. 311 | * Note that we use decor view of the activity so that the typeface will also be applied to action bar. 312 | * @param activity the activity. 313 | * @param typefaceName typeface name. 314 | */ 315 | public void setTypeface(Activity activity, String typefaceName) { 316 | setTypeface(activity, typefaceName, 0); 317 | } 318 | 319 | /** 320 | * Set the typeface to the all text views belong to the activity. 321 | * Note that we use decor view of the activity so that the typeface will also be applied to action bar. 322 | * @param activity the activity. 323 | * @param strResId string resource containing typeface name. 324 | */ 325 | public void setTypeface(Activity activity, @StringRes int strResId) { 326 | setTypeface(activity, mApplication.getString(strResId)); 327 | } 328 | 329 | /** 330 | * Set the typeface to the all text views belong to the activity. 331 | * Note that we use decor view of the activity so that the typeface will also be applied to action bar. 332 | * @param activity the activity. 333 | * @param typefaceName typeface name. 334 | * @param style the typeface style. 335 | */ 336 | public void setTypeface(Activity activity, String typefaceName, int style) { 337 | setTypeface((ViewGroup) activity.getWindow().getDecorView(), typefaceName, style); 338 | } 339 | 340 | /** 341 | * Set the typeface to the all text views belong to the activity. 342 | * Note that we use decor view of the activity so that the typeface will also be applied to action bar. 343 | * @param activity the activity. 344 | * @param strResId string resource containing typeface name. 345 | * @param style the typeface style. 346 | */ 347 | public void setTypeface(Activity activity, @StringRes int strResId, int style) { 348 | setTypeface(activity, mApplication.getString(strResId), style); 349 | } 350 | 351 | /** 352 | * Set the typeface to the all text views belong to the fragment. 353 | * Make sure to call this method after fragment view creation. 354 | * If you use fragments in the support package, 355 | * @param fragment the fragment. 356 | * @param typefaceName typeface name. 357 | */ 358 | @TargetApi(Build.VERSION_CODES.HONEYCOMB) 359 | public void setTypeface(F fragment, String typefaceName) { 360 | setTypeface(fragment, typefaceName, 0); 361 | } 362 | 363 | /** 364 | * Set the typeface to the all text views belong to the fragment. 365 | * Make sure to call this method after fragment view creation. 366 | * If you use fragments in the support package, 367 | * @param fragment the fragment. 368 | * @param strResId string resource containing typeface name. 369 | */ 370 | @TargetApi(Build.VERSION_CODES.HONEYCOMB) 371 | public void setTypeface(F fragment, @StringRes int strResId) { 372 | setTypeface(fragment, mApplication.getString(strResId)); 373 | } 374 | 375 | /** 376 | * Set the typeface to the all text views belong to the fragment. 377 | * Make sure to call this method after fragment view creation. 378 | * If you use fragments in the support package, 379 | * @param fragment the fragment. 380 | * @param typefaceName typeface name. 381 | * @param style the typeface style. 382 | */ 383 | @TargetApi(Build.VERSION_CODES.HONEYCOMB) 384 | public void setTypeface(F fragment, String typefaceName, int style) { 385 | View root = fragment.getView(); 386 | if (root instanceof TextView) { 387 | setTypeface((TextView) root, typefaceName, style); 388 | } else if (root instanceof ViewGroup) { 389 | setTypeface((ViewGroup) root, typefaceName, style); 390 | } 391 | } 392 | 393 | /** 394 | * Set the typeface to the all text views belong to the fragment. 395 | * Make sure to call this method after fragment view creation. 396 | * If you use fragments in the support package, 397 | * @param fragment the fragment. 398 | * @param strResId string resource containing typeface name. 399 | * @param style the typeface style. 400 | */ 401 | @TargetApi(Build.VERSION_CODES.HONEYCOMB) 402 | public void setTypeface(F fragment, @StringRes int strResId, int style) { 403 | setTypeface(fragment, mApplication.getString(strResId), style); 404 | } 405 | 406 | /** 407 | * Set the typeface to the all text views belong to the fragment. 408 | * Make sure to call this method after fragment view creation. 409 | * And this is a support package fragments only. 410 | * @param fragment the fragment. 411 | * @param typefaceName typeface name. 412 | */ 413 | public void supportSetTypeface(F fragment, String typefaceName) { 414 | supportSetTypeface(fragment, typefaceName, 0); 415 | } 416 | 417 | /** 418 | * Set the typeface to the all text views belong to the fragment. 419 | * Make sure to call this method after fragment view creation. 420 | * And this is a support package fragments only. 421 | * @param fragment the fragment. 422 | * @param strResId string resource containing typeface name. 423 | */ 424 | public void supportSetTypeface(F fragment, @StringRes int strResId) { 425 | supportSetTypeface(fragment, mApplication.getString(strResId)); 426 | } 427 | 428 | /** 429 | * Set the typeface to the all text views belong to the fragment. 430 | * Make sure to call this method after fragment view creation. 431 | * And this is a support package fragments only. 432 | * @param fragment the fragment. 433 | * @param typefaceName typeface name. 434 | * @param style the typeface style. 435 | */ 436 | public void supportSetTypeface(F fragment, String typefaceName, int style) { 437 | View root = fragment.getView(); 438 | if (root instanceof TextView) { 439 | setTypeface((TextView) root, typefaceName, style); 440 | } else if (root instanceof ViewGroup) { 441 | setTypeface((ViewGroup) root, typefaceName, style); 442 | } 443 | } 444 | 445 | /** 446 | * Set the typeface to the all text views belong to the fragment. 447 | * Make sure to call this method after fragment view creation. 448 | * And this is a support package fragments only. 449 | * @param fragment the fragment. 450 | * @param strResId string resource containing typeface name. 451 | * @param style the typeface style. 452 | */ 453 | public void supportSetTypeface(F fragment, @StringRes int strResId, int style) { 454 | supportSetTypeface(fragment, mApplication.getString(strResId), style); 455 | } 456 | 457 | /** 458 | * Set the typeface for the dialog view. 459 | * @param dialog the dialog. 460 | * @param typefaceName typeface name. 461 | */ 462 | public void setTypeface(D dialog, String typefaceName) { 463 | setTypeface(dialog, typefaceName, 0); 464 | } 465 | 466 | /** 467 | * Set the typeface for the dialog view. 468 | * @param dialog the dialog. 469 | * @param strResId string resource containing typeface name. 470 | */ 471 | public void setTypeface(D dialog, @StringRes int strResId) { 472 | setTypeface(dialog, mApplication.getString(strResId)); 473 | } 474 | 475 | /** 476 | * Set the typeface for the dialog view. 477 | * @param dialog the dialog. 478 | * @param typefaceName typeface name. 479 | * @param style the typeface style. 480 | */ 481 | public void setTypeface(D dialog, String typefaceName, int style) { 482 | DialogUtils.setTypeface(this, dialog, typefaceName, style); 483 | } 484 | 485 | /** 486 | * Set the typeface for the dialog view. 487 | * @param dialog the dialog. 488 | * @param strResId string resource containing typeface name. 489 | * @param style the typeface style. 490 | */ 491 | public void setTypeface(D dialog, @StringRes int strResId, int style) { 492 | setTypeface(dialog, mApplication.getString(strResId), style); 493 | } 494 | 495 | /** 496 | * Set the typeface for the toast view. 497 | * @param toast toast. 498 | * @param typefaceName typeface name. 499 | * @return toast that the typeface is injected. 500 | */ 501 | public Toast setTypeface(Toast toast, String typefaceName) { 502 | return setTypeface(toast, typefaceName, 0); 503 | } 504 | 505 | /** 506 | * Set the typeface for the toast view. 507 | * @param toast toast. 508 | * @param strResId string resource containing typeface name. 509 | * @return toast that the typeface is injected. 510 | */ 511 | public Toast setTypeface(Toast toast, @StringRes int strResId) { 512 | return setTypeface(toast, mApplication.getString(strResId)); 513 | } 514 | 515 | /** 516 | * Set the typeface for the toast view. 517 | * @param toast toast. 518 | * @param typefaceName typeface name. 519 | * @param style the typeface style. 520 | * @return toast that the typeface is injected. 521 | */ 522 | public Toast setTypeface(Toast toast, String typefaceName, int style) { 523 | setTypeface((ViewGroup) toast.getView(), typefaceName, style); 524 | return toast; 525 | } 526 | 527 | /** 528 | * Set the typeface for the toast view. 529 | * @param toast toast. 530 | * @param strResId string resource containing typeface name. 531 | * @param style the typeface style. 532 | * @return toast that the typeface is injected. 533 | */ 534 | public Toast setTypeface(Toast toast, @StringRes int strResId, int style) { 535 | return setTypeface(toast, mApplication.getString(strResId), style); 536 | } 537 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/toolbar_shadow.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_contacts_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 15 | 16 | 24 | 25 | 29 | 30 | 31 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 |