├── settings.gradle ├── gradle.properties ├── .gitignore ├── PeriodicTable ├── src │ └── main │ │ ├── assets │ │ ├── fonts │ │ │ └── NotoSans-Regular.ttf │ │ └── html │ │ │ └── licenses.html │ │ ├── res │ │ ├── drawable-nodpi │ │ │ └── backdrop.png │ │ ├── drawable-hdpi │ │ │ ├── ic_menu_search.png │ │ │ ├── ic_menu_wikipedia.png │ │ │ └── ic_launcher_periodic_table.png │ │ ├── drawable-mdpi │ │ │ ├── ic_menu_search.png │ │ │ ├── ic_menu_wikipedia.png │ │ │ └── ic_launcher_periodic_table.png │ │ ├── drawable-xhdpi │ │ │ ├── ic_menu_search.png │ │ │ ├── ic_menu_wikipedia.png │ │ │ └── ic_launcher_periodic_table.png │ │ ├── drawable-xxhdpi │ │ │ ├── ic_menu_search.png │ │ │ ├── ic_menu_wikipedia.png │ │ │ └── ic_launcher_periodic_table.png │ │ ├── values-sw600dp-land │ │ │ └── dimens.xml │ │ ├── values │ │ │ ├── attrs.xml │ │ │ ├── ids.xml │ │ │ ├── urls.xml │ │ │ ├── dimens.xml │ │ │ ├── colors.xml │ │ │ ├── styles.xml │ │ │ └── strings.xml │ │ ├── drawable-xxxhdpi │ │ │ ├── ic_menu_search.png │ │ │ ├── ic_menu_wikipedia.png │ │ │ └── ic_launcher_periodic_table.png │ │ ├── values-land │ │ │ └── dimens.xml │ │ ├── values-sw720dp │ │ │ └── dimens.xml │ │ ├── values-sw720dp-land │ │ │ └── dimens.xml │ │ ├── menu │ │ │ ├── properties_context_menu.xml │ │ │ ├── main_menu.xml │ │ │ ├── elements_menu.xml │ │ │ └── properties_action_menu.xml │ │ ├── drawable │ │ │ ├── table_item_background_selector.xml │ │ │ ├── properties_toolbar_background.xml │ │ │ └── table_item_background.xml │ │ ├── values-sw600dp │ │ │ └── dimens.xml │ │ ├── layout │ │ │ ├── table_text.xml │ │ │ ├── table_active_item.xml │ │ │ ├── table_fragment.xml │ │ │ ├── properties_fragment.xml │ │ │ ├── properties_list_header.xml │ │ │ ├── progress_bar.xml │ │ │ ├── elements_list_fragment.xml │ │ │ ├── main_activity.xml │ │ │ ├── properties_list_item.xml │ │ │ ├── elements_list_item.xml │ │ │ ├── table_item.xml │ │ │ ├── isotope_list_item.xml │ │ │ └── properties_activity.xml │ │ ├── drawable-v21 │ │ │ └── table_item_background.xml │ │ ├── values-v21 │ │ │ └── styles.xml │ │ ├── layout-v17 │ │ │ └── properties_list_header.xml │ │ └── xml │ │ │ ├── settings.xml │ │ │ └── settings_about.xml │ │ ├── java │ │ └── com │ │ │ └── frozendevs │ │ │ └── periodictable │ │ │ ├── model │ │ │ ├── TableItem.java │ │ │ ├── TableTextItem.java │ │ │ ├── ElementListItem.java │ │ │ ├── TableElementItem.java │ │ │ ├── Isotope.java │ │ │ ├── adapter │ │ │ │ ├── ViewPagerAdapter.java │ │ │ │ ├── ElementsAdapter.java │ │ │ │ ├── PropertiesAdapter.java │ │ │ │ ├── TableAdapter.java │ │ │ │ └── IsotopesAdapter.java │ │ │ └── ElementProperties.java │ │ │ ├── widget │ │ │ ├── AppBarLayout.java │ │ │ ├── DividerDecoration.java │ │ │ └── Zoomer.java │ │ │ ├── PeriodicTableApplication.java │ │ │ ├── content │ │ │ ├── AsyncTaskLoader.java │ │ │ └── Database.java │ │ │ ├── activity │ │ │ ├── MainActivity.java │ │ │ ├── SettingsActivity.java │ │ │ └── PropertiesActivity.java │ │ │ ├── fragment │ │ │ ├── PropertiesFragment.java │ │ │ ├── IsotopesFragment.java │ │ │ ├── ElementsFragment.java │ │ │ └── TableFragment.java │ │ │ └── view │ │ │ ├── ExpandableIndicatorView.java │ │ │ ├── RecyclerView.java │ │ │ ├── PeriodicTableView.java │ │ │ └── ZoomableScrollView.java │ │ └── AndroidManifest.xml ├── proguard-rules.pro └── build.gradle ├── .gitlab-ci.yml ├── .travis.yml └── README.md /settings.gradle: -------------------------------------------------------------------------------- 1 | include 'PeriodicTable' -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Set Gradle settings which apply to all modules here. -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | local.properties 3 | .idea 4 | .DS_Store 5 | *.iml 6 | build/ 7 | -------------------------------------------------------------------------------- /PeriodicTable/src/main/assets/fonts/NotoSans-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Frozen-Developers/android-periodic-table/HEAD/PeriodicTable/src/main/assets/fonts/NotoSans-Regular.ttf -------------------------------------------------------------------------------- /PeriodicTable/src/main/res/drawable-nodpi/backdrop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Frozen-Developers/android-periodic-table/HEAD/PeriodicTable/src/main/res/drawable-nodpi/backdrop.png -------------------------------------------------------------------------------- /PeriodicTable/src/main/java/com/frozendevs/periodictable/model/TableItem.java: -------------------------------------------------------------------------------- 1 | package com.frozendevs.periodictable.model; 2 | 3 | public interface TableItem { 4 | int getCategory(); 5 | } 6 | -------------------------------------------------------------------------------- /PeriodicTable/src/main/res/drawable-hdpi/ic_menu_search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Frozen-Developers/android-periodic-table/HEAD/PeriodicTable/src/main/res/drawable-hdpi/ic_menu_search.png -------------------------------------------------------------------------------- /PeriodicTable/src/main/res/drawable-mdpi/ic_menu_search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Frozen-Developers/android-periodic-table/HEAD/PeriodicTable/src/main/res/drawable-mdpi/ic_menu_search.png -------------------------------------------------------------------------------- /PeriodicTable/src/main/res/drawable-xhdpi/ic_menu_search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Frozen-Developers/android-periodic-table/HEAD/PeriodicTable/src/main/res/drawable-xhdpi/ic_menu_search.png -------------------------------------------------------------------------------- /PeriodicTable/src/main/res/drawable-xxhdpi/ic_menu_search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Frozen-Developers/android-periodic-table/HEAD/PeriodicTable/src/main/res/drawable-xxhdpi/ic_menu_search.png -------------------------------------------------------------------------------- /PeriodicTable/src/main/res/values-sw600dp-land/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 312dp 5 | 6 | 7 | -------------------------------------------------------------------------------- /PeriodicTable/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /PeriodicTable/src/main/res/drawable-hdpi/ic_menu_wikipedia.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Frozen-Developers/android-periodic-table/HEAD/PeriodicTable/src/main/res/drawable-hdpi/ic_menu_wikipedia.png -------------------------------------------------------------------------------- /PeriodicTable/src/main/res/drawable-mdpi/ic_menu_wikipedia.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Frozen-Developers/android-periodic-table/HEAD/PeriodicTable/src/main/res/drawable-mdpi/ic_menu_wikipedia.png -------------------------------------------------------------------------------- /PeriodicTable/src/main/res/drawable-xhdpi/ic_menu_wikipedia.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Frozen-Developers/android-periodic-table/HEAD/PeriodicTable/src/main/res/drawable-xhdpi/ic_menu_wikipedia.png -------------------------------------------------------------------------------- /PeriodicTable/src/main/res/drawable-xxxhdpi/ic_menu_search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Frozen-Developers/android-periodic-table/HEAD/PeriodicTable/src/main/res/drawable-xxxhdpi/ic_menu_search.png -------------------------------------------------------------------------------- /PeriodicTable/src/main/res/values-land/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 48dp 5 | 6 | 7 | -------------------------------------------------------------------------------- /PeriodicTable/src/main/res/drawable-xxhdpi/ic_menu_wikipedia.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Frozen-Developers/android-periodic-table/HEAD/PeriodicTable/src/main/res/drawable-xxhdpi/ic_menu_wikipedia.png -------------------------------------------------------------------------------- /PeriodicTable/src/main/res/drawable-xxxhdpi/ic_menu_wikipedia.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Frozen-Developers/android-periodic-table/HEAD/PeriodicTable/src/main/res/drawable-xxxhdpi/ic_menu_wikipedia.png -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | stages: 2 | - build 3 | 4 | build: 5 | stage: build 6 | script: 7 | - gradle build 8 | artifacts: 9 | paths: 10 | - CacheCleaner/build/outputs/ 11 | -------------------------------------------------------------------------------- /PeriodicTable/src/main/res/drawable-hdpi/ic_launcher_periodic_table.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Frozen-Developers/android-periodic-table/HEAD/PeriodicTable/src/main/res/drawable-hdpi/ic_launcher_periodic_table.png -------------------------------------------------------------------------------- /PeriodicTable/src/main/res/drawable-mdpi/ic_launcher_periodic_table.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Frozen-Developers/android-periodic-table/HEAD/PeriodicTable/src/main/res/drawable-mdpi/ic_launcher_periodic_table.png -------------------------------------------------------------------------------- /PeriodicTable/src/main/res/drawable-xhdpi/ic_launcher_periodic_table.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Frozen-Developers/android-periodic-table/HEAD/PeriodicTable/src/main/res/drawable-xhdpi/ic_launcher_periodic_table.png -------------------------------------------------------------------------------- /PeriodicTable/src/main/res/drawable-xxhdpi/ic_launcher_periodic_table.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Frozen-Developers/android-periodic-table/HEAD/PeriodicTable/src/main/res/drawable-xxhdpi/ic_launcher_periodic_table.png -------------------------------------------------------------------------------- /PeriodicTable/src/main/res/drawable-xxxhdpi/ic_launcher_periodic_table.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Frozen-Developers/android-periodic-table/HEAD/PeriodicTable/src/main/res/drawable-xxxhdpi/ic_launcher_periodic_table.png -------------------------------------------------------------------------------- /PeriodicTable/src/main/res/values-sw720dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 64dp 5 | 0dp 6 | 7 | 8 | -------------------------------------------------------------------------------- /PeriodicTable/src/main/res/values-sw720dp-land/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 112dp 5 | 0dp 6 | 7 | 8 | -------------------------------------------------------------------------------- /PeriodicTable/src/main/res/menu/properties_context_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | -------------------------------------------------------------------------------- /PeriodicTable/src/main/res/values/ids.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /PeriodicTable/src/main/res/values/urls.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | https://github.com/Frozen-Developers 4 | https://www.facebook.com/samuelsgraphics 5 | -------------------------------------------------------------------------------- /PeriodicTable/src/main/res/drawable/table_item_background_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /PeriodicTable/src/main/res/drawable/properties_toolbar_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /PeriodicTable/src/main/res/values-sw600dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 0dp 5 | 0dp 6 | 7 | 48dp 8 | 9 | 388dp 10 | 11 | 32dp 12 | 13 | 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: android 2 | jdk: oraclejdk7 3 | sudo: false 4 | 5 | before_script: 6 | - wget https://services.gradle.org/distributions/gradle-2.12-bin.zip 7 | - unzip gradle-2.12-bin.zip 8 | - export GRADLE_HOME=$PWD/gradle-2.12 9 | - export PATH=$GRADLE_HOME/bin:$PATH 10 | 11 | android: 12 | components: 13 | - tools 14 | - platform-tools 15 | - build-tools-23.0.3 16 | - android-23 17 | - extra-android-m2repository 18 | 19 | script: gradle build 20 | -------------------------------------------------------------------------------- /PeriodicTable/src/main/res/drawable/table_item_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /PeriodicTable/src/main/res/layout/table_text.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 | -------------------------------------------------------------------------------- /PeriodicTable/src/main/res/menu/main_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /PeriodicTable/src/main/res/menu/elements_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /PeriodicTable/src/main/res/drawable-v21/table_item_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /PeriodicTable/src/main/res/menu/properties_action_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 13 | 14 | -------------------------------------------------------------------------------- /PeriodicTable/src/main/res/values-v21/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /PeriodicTable/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 0dp 5 | 0dp 6 | 7 | 95dp 8 | 9 | 8dp 10 | 8dp 11 | 12 | 8dp 13 | 14 | 176dp 15 | 16 | 20dp 17 | 18 | 19 | -------------------------------------------------------------------------------- /PeriodicTable/src/main/java/com/frozendevs/periodictable/widget/AppBarLayout.java: -------------------------------------------------------------------------------- 1 | package com.frozendevs.periodictable.widget; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | 6 | public class AppBarLayout extends android.support.design.widget.AppBarLayout { 7 | public AppBarLayout(Context context) { 8 | super(context); 9 | } 10 | 11 | public AppBarLayout(Context context, AttributeSet attrs) { 12 | super(context, attrs); 13 | } 14 | 15 | @Override 16 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 17 | super.onMeasure(widthMeasureSpec, 0); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /PeriodicTable/src/main/res/layout/table_active_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /PeriodicTable/src/main/res/layout/table_fragment.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /PeriodicTable/src/main/res/layout/properties_fragment.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | -------------------------------------------------------------------------------- /PeriodicTable/src/main/res/layout/properties_list_header.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | -------------------------------------------------------------------------------- /PeriodicTable/src/main/res/layout-v17/properties_list_header.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | -------------------------------------------------------------------------------- /PeriodicTable/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 /opt/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 | -dontobfuscate 13 | 14 | # SearchView 15 | -keep class android.support.v7.widget.SearchView { *; } 16 | 17 | # AppBarLayout.ScrollingViewBehavior 18 | -keepattributes *Annotation* 19 | -keep public class * extends android.support.design.widget.CoordinatorLayout.Behavior { *; } 20 | -keep public class * extends android.support.design.widget.ViewOffsetBehavior { *; } 21 | 22 | # Picasso 23 | -dontwarn com.squareup.picasso.** 24 | -------------------------------------------------------------------------------- /PeriodicTable/src/main/res/layout/progress_bar.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 14 | 15 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /PeriodicTable/src/main/java/com/frozendevs/periodictable/PeriodicTableApplication.java: -------------------------------------------------------------------------------- 1 | package com.frozendevs.periodictable; 2 | 3 | import android.app.Application; 4 | import android.support.v4.app.SharedElementCallback; 5 | import android.view.View; 6 | 7 | public class PeriodicTableApplication extends Application { 8 | private SharedElementCallback mSharedElementCallback; 9 | private View.OnAttachStateChangeListener mOnAttachStateChangeListener; 10 | 11 | public SharedElementCallback getSharedElementCallback() { 12 | return mSharedElementCallback; 13 | } 14 | 15 | public void setSharedElementCallback(SharedElementCallback callback) { 16 | mSharedElementCallback = callback; 17 | } 18 | 19 | public View.OnAttachStateChangeListener getOnAttachStateChangeListener() { 20 | return mOnAttachStateChangeListener; 21 | } 22 | 23 | public void setOnAttachStateChangeListener(View.OnAttachStateChangeListener listener) { 24 | this.mOnAttachStateChangeListener = listener; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Periodic Table [![Build Status](https://travis-ci.org/Frozen-Developers/android-periodic-table.svg)](https://travis-ci.org/Frozen-Developers/android-periodic-table) 2 | ============== 3 | 4 | An ad-free, open source, holo styled interactive periodic table with list of element properties and list of isotopes based on Wikipedia's database. 5 | 6 | build_db.py 7 | ----------- 8 | 9 | You can rebuild the database anytime you want using build_db.py script. The script depends on python 3 and on the internet connection. 10 | 11 | database.json 12 | ------------- 13 | 14 | This file uses material from the Wikipedia article "Periodic table", which is released under the Creative Commons Attribution-Share-Alike License 3.0. 15 | 16 | Download 17 | -------- 18 | 19 | [Google Play Store](https://play.google.com/store/apps/details?id=com.frozendevs.periodictable) 20 | [![F-Droid](https://guardianproject.info/wp-content/uploads/2014/07/logo-fdroid.png)](https://f-droid.org/repository/browse/?fdid=com.frozendevs.periodictable) 21 | -------------------------------------------------------------------------------- /PeriodicTable/src/main/res/xml/settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /PeriodicTable/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | #ff212121 5 | @android:color/black 6 | #ff80cbc4 7 | 8 | #ff99cc 9 | #ff6666 10 | #ffdead 11 | #e7ff8f 12 | #ffbfff 13 | #cccc99 14 | #c0ffff 15 | #a1ffc3 16 | #cccccc 17 | #ffc0c0 18 | #e8e8e8 19 | 20 | #40000000 21 | 22 | #33ffffff 23 | #1f000000 24 | 25 | 26 | -------------------------------------------------------------------------------- /PeriodicTable/src/main/res/layout/elements_list_fragment.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 | 20 | 21 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /PeriodicTable/src/main/java/com/frozendevs/periodictable/model/TableTextItem.java: -------------------------------------------------------------------------------- 1 | package com.frozendevs.periodictable.model; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | 6 | public class TableTextItem implements TableItem, Parcelable { 7 | private String text; 8 | private int category; 9 | 10 | public static final Creator CREATOR = new Creator() { 11 | @Override 12 | public TableTextItem createFromParcel(Parcel in) { 13 | return new TableTextItem(in); 14 | } 15 | 16 | @Override 17 | public TableTextItem[] newArray(int size) { 18 | return new TableTextItem[size]; 19 | } 20 | }; 21 | 22 | public TableTextItem(String text, int category) { 23 | this.text = text; 24 | this.category = category; 25 | } 26 | 27 | private TableTextItem(Parcel in) { 28 | text = in.readString(); 29 | category = in.readInt(); 30 | } 31 | 32 | @Override 33 | public int describeContents() { 34 | return 0; 35 | } 36 | 37 | @Override 38 | public void writeToParcel(Parcel dest, int flags) { 39 | dest.writeString(text); 40 | dest.writeInt(category); 41 | } 42 | 43 | public String getText() { 44 | return text; 45 | } 46 | 47 | @Override 48 | public int getCategory() { 49 | return category; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /PeriodicTable/src/main/java/com/frozendevs/periodictable/model/ElementListItem.java: -------------------------------------------------------------------------------- 1 | package com.frozendevs.periodictable.model; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | 6 | public class ElementListItem implements Parcelable { 7 | 8 | private String name, symbol; 9 | private int number; 10 | 11 | public static final Parcelable.Creator CREATOR 12 | = new Parcelable.Creator() { 13 | public ElementListItem createFromParcel(Parcel in) { 14 | return new ElementListItem(in); 15 | } 16 | 17 | public ElementListItem[] newArray(int size) { 18 | return new ElementListItem[size]; 19 | } 20 | }; 21 | 22 | protected ElementListItem(Parcel in) { 23 | name = in.readString(); 24 | symbol = in.readString(); 25 | number = in.readInt(); 26 | } 27 | 28 | @Override 29 | public int describeContents() { 30 | return 0; 31 | } 32 | 33 | @Override 34 | public void writeToParcel(Parcel dest, int flags) { 35 | dest.writeString(name); 36 | dest.writeString(symbol); 37 | dest.writeInt(number); 38 | } 39 | 40 | public String getName() { 41 | return name; 42 | } 43 | 44 | public String getSymbol() { 45 | return symbol; 46 | } 47 | 48 | public int getNumber() { 49 | return number; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /PeriodicTable/src/main/res/layout/main_activity.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 13 | 14 | 19 | 20 | 28 | 29 | 30 | 31 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /PeriodicTable/src/main/res/xml/settings_about.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 13 | 14 | 17 | 18 | 19 | 20 | 23 | 24 | 27 | 28 | 29 | 30 | 31 | 32 | 36 | 37 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /PeriodicTable/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 20 | 21 | 30 | 31 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /PeriodicTable/src/main/java/com/frozendevs/periodictable/model/TableElementItem.java: -------------------------------------------------------------------------------- 1 | package com.frozendevs.periodictable.model; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | 6 | public class TableElementItem extends ElementListItem implements TableItem { 7 | 8 | private String weight; 9 | private int group, period, category; 10 | 11 | public static final Parcelable.Creator CREATOR 12 | = new Parcelable.Creator() { 13 | public TableElementItem createFromParcel(Parcel in) { 14 | return new TableElementItem(in); 15 | } 16 | 17 | public TableElementItem[] newArray(int size) { 18 | return new TableElementItem[size]; 19 | } 20 | }; 21 | 22 | protected TableElementItem(Parcel in) { 23 | super(in); 24 | 25 | group = in.readInt(); 26 | period = in.readInt(); 27 | category = in.readInt(); 28 | weight = in.readString(); 29 | } 30 | 31 | @Override 32 | public void writeToParcel(Parcel dest, int flags) { 33 | super.writeToParcel(dest, flags); 34 | 35 | dest.writeInt(group); 36 | dest.writeInt(period); 37 | dest.writeInt(category); 38 | dest.writeString(weight); 39 | } 40 | 41 | public int getGroup() { 42 | return group; 43 | } 44 | 45 | public int getPeriod() { 46 | return period; 47 | } 48 | 49 | public String getStandardAtomicWeight() { 50 | return weight; 51 | } 52 | 53 | @Override 54 | public int getCategory() { 55 | return category; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /PeriodicTable/src/main/res/layout/properties_list_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 27 | 28 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /PeriodicTable/src/main/java/com/frozendevs/periodictable/model/Isotope.java: -------------------------------------------------------------------------------- 1 | package com.frozendevs.periodictable.model; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | 6 | public class Isotope implements Parcelable { 7 | 8 | private String symbol, halfLife, spin, abundance, decayModes; 9 | 10 | public static final Parcelable.Creator CREATOR 11 | = new Parcelable.Creator() { 12 | public Isotope createFromParcel(Parcel in) { 13 | return new Isotope(in); 14 | } 15 | 16 | public Isotope[] newArray(int size) { 17 | return new Isotope[size]; 18 | } 19 | }; 20 | 21 | protected Isotope(Parcel in) { 22 | symbol = in.readString(); 23 | halfLife = in.readString(); 24 | spin = in.readString(); 25 | abundance = in.readString(); 26 | decayModes = in.readString(); 27 | } 28 | 29 | @Override 30 | public int describeContents() { 31 | return 0; 32 | } 33 | 34 | @Override 35 | public void writeToParcel(Parcel dest, int flags) { 36 | dest.writeString(symbol); 37 | dest.writeString(halfLife); 38 | dest.writeString(spin); 39 | dest.writeString(abundance); 40 | dest.writeString(decayModes); 41 | } 42 | 43 | public String getSymbol() { 44 | return symbol; 45 | } 46 | 47 | public String getHalfLife() { 48 | return halfLife; 49 | } 50 | 51 | public String getSpin() { 52 | return spin; 53 | } 54 | 55 | public String getAbundance() { 56 | return abundance; 57 | } 58 | 59 | public String getDecayModes() { 60 | return decayModes; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /PeriodicTable/src/main/java/com/frozendevs/periodictable/content/AsyncTaskLoader.java: -------------------------------------------------------------------------------- 1 | package com.frozendevs.periodictable.content; 2 | 3 | import android.content.Context; 4 | 5 | public abstract class AsyncTaskLoader extends 6 | android.support.v4.content.AsyncTaskLoader { 7 | private Result mResult; 8 | 9 | public AsyncTaskLoader(Context context) { 10 | super(context); 11 | } 12 | 13 | @Override 14 | public void deliverResult(Result result) { 15 | if (isReset() && mResult != null) { 16 | releaseResources(result); 17 | 18 | return; 19 | } 20 | 21 | Result oldResult = mResult; 22 | mResult = result; 23 | 24 | if (isStarted()) { 25 | super.deliverResult(result); 26 | } 27 | 28 | if (oldResult != null && oldResult != result) { 29 | releaseResources(oldResult); 30 | } 31 | } 32 | 33 | @Override 34 | protected void onStartLoading() { 35 | if (mResult != null) { 36 | deliverResult(mResult); 37 | } 38 | 39 | if (takeContentChanged() || mResult == null) { 40 | forceLoad(); 41 | } 42 | } 43 | 44 | @Override 45 | protected void onStopLoading() { 46 | cancelLoad(); 47 | } 48 | 49 | @Override 50 | protected void onReset() { 51 | onStopLoading(); 52 | 53 | if (mResult != null) { 54 | releaseResources(mResult); 55 | 56 | mResult = null; 57 | } 58 | } 59 | 60 | @Override 61 | public void onCanceled(Result result) { 62 | super.onCanceled(result); 63 | 64 | releaseResources(result); 65 | } 66 | 67 | public void releaseResources(Result result) { 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /PeriodicTable/src/main/java/com/frozendevs/periodictable/model/adapter/ViewPagerAdapter.java: -------------------------------------------------------------------------------- 1 | package com.frozendevs.periodictable.model.adapter; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | import android.support.v4.app.Fragment; 6 | import android.support.v4.app.FragmentPagerAdapter; 7 | import android.support.v7.app.AppCompatActivity; 8 | 9 | import java.util.ArrayList; 10 | 11 | public class ViewPagerAdapter extends FragmentPagerAdapter { 12 | 13 | private Context mContext; 14 | private ArrayList mPages = new ArrayList(); 15 | 16 | static final class PageInfo { 17 | private final String title; 18 | private final Class cls; 19 | private final Bundle args; 20 | 21 | PageInfo(String _title, Class _class, Bundle _args) { 22 | title = _title; 23 | cls = _class; 24 | args = _args; 25 | } 26 | } 27 | 28 | public ViewPagerAdapter(AppCompatActivity activity) { 29 | super(activity.getSupportFragmentManager()); 30 | 31 | mContext = activity; 32 | } 33 | 34 | public void addPage(int title, Class cls, Bundle args) { 35 | addPage(mContext.getString(title), cls, args); 36 | } 37 | 38 | public void addPage(String title, Class cls, Bundle args) { 39 | mPages.add(new PageInfo(title, cls, args)); 40 | 41 | notifyDataSetChanged(); 42 | } 43 | 44 | @Override 45 | public int getCount() { 46 | return mPages.size(); 47 | } 48 | 49 | @Override 50 | public Fragment getItem(int position) { 51 | PageInfo info = mPages.get(position); 52 | 53 | return Fragment.instantiate(mContext, info.cls.getName(), info.args); 54 | } 55 | 56 | @Override 57 | public CharSequence getPageTitle(int position) { 58 | return mPages.get(position).title; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /PeriodicTable/build.gradle: -------------------------------------------------------------------------------- 1 | import org.ajoberstar.grgit.Grgit 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:2.1.0' 9 | classpath 'org.ajoberstar:gradle-git:1.4.2' 10 | } 11 | } 12 | apply plugin: 'com.android.application' 13 | 14 | repositories { 15 | jcenter() 16 | } 17 | 18 | ext { 19 | git = Grgit.open() 20 | gitVersionCode = git.tag.list().size() + 2 21 | gitVersionName = "${git.describe()}" 22 | } 23 | 24 | android { 25 | compileSdkVersion 23 26 | buildToolsVersion '23.0.3' 27 | 28 | defaultConfig { 29 | applicationId "com.frozendevs.periodictable" 30 | minSdkVersion 15 31 | targetSdkVersion 23 32 | versionCode = gitVersionCode 33 | versionName = gitVersionName 34 | 35 | vectorDrawables.useSupportLibrary = true 36 | } 37 | 38 | compileOptions { 39 | sourceCompatibility JavaVersion.VERSION_1_7 40 | targetCompatibility JavaVersion.VERSION_1_7 41 | } 42 | 43 | buildTypes { 44 | release { 45 | minifyEnabled true 46 | shrinkResources true 47 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 48 | } 49 | } 50 | 51 | lintOptions { 52 | ignore 'GoogleAppIndexingWarning', 'UnusedAttribute' 53 | textOutput 'stdout' 54 | textReport true 55 | } 56 | } 57 | 58 | dependencies { 59 | compile 'com.android.support:support-v4:23.3.0' 60 | compile 'com.android.support:appcompat-v7:23.3.0@aar' 61 | compile 'com.android.support:design:23.3.0@aar' 62 | compile 'com.android.support:recyclerview-v7:23.3.0@aar' 63 | compile 'com.android.support:support-vector-drawable:23.3.0@aar' 64 | compile 'com.google.code.gson:gson:2.6.2' 65 | compile 'com.h6ah4i.android.widget.advrecyclerview:advrecyclerview:0.8.7@aar' 66 | compile 'com.squareup.picasso:picasso:2.5.2' 67 | } 68 | 69 | if (project.hasProperty("PeriodicTable.signing") && 70 | file(project.property("PeriodicTable.signing") + ".gradle").exists()) { 71 | apply from: project.property("PeriodicTable.signing") + ".gradle"; 72 | } 73 | -------------------------------------------------------------------------------- /PeriodicTable/src/main/java/com/frozendevs/periodictable/activity/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.frozendevs.periodictable.activity; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.support.design.widget.TabLayout; 6 | import android.support.v4.view.ViewPager; 7 | import android.support.v7.app.AppCompatActivity; 8 | import android.support.v7.widget.Toolbar; 9 | import android.view.Menu; 10 | import android.view.MenuItem; 11 | 12 | import com.frozendevs.periodictable.R; 13 | import com.frozendevs.periodictable.fragment.ElementsFragment; 14 | import com.frozendevs.periodictable.fragment.TableFragment; 15 | import com.frozendevs.periodictable.model.adapter.ViewPagerAdapter; 16 | 17 | public class MainActivity extends AppCompatActivity { 18 | 19 | @Override 20 | protected void onCreate(Bundle savedInstanceState) { 21 | super.onCreate(savedInstanceState); 22 | 23 | setContentView(R.layout.main_activity); 24 | 25 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); 26 | 27 | setSupportActionBar(toolbar); 28 | 29 | getSupportActionBar().setDisplayHomeAsUpEnabled(false); 30 | 31 | ViewPagerAdapter pagerAdapter = new ViewPagerAdapter(this); 32 | pagerAdapter.addPage(R.string.fragment_title_elements, ElementsFragment.class, null); 33 | pagerAdapter.addPage(R.string.fragment_title_table, TableFragment.class, null); 34 | 35 | ViewPager viewPager = (ViewPager) findViewById(R.id.pager); 36 | viewPager.setAdapter(pagerAdapter); 37 | 38 | TabLayout tabLayout = (TabLayout) findViewById(R.id.tabs); 39 | tabLayout.setupWithViewPager(viewPager); 40 | } 41 | 42 | @Override 43 | public boolean onCreateOptionsMenu(Menu menu) { 44 | getMenuInflater().inflate(R.menu.main_menu, menu); 45 | 46 | return super.onCreateOptionsMenu(menu); 47 | } 48 | 49 | @Override 50 | public boolean onOptionsItemSelected(MenuItem item) { 51 | switch (item.getItemId()) { 52 | case R.id.action_about: 53 | startActivity(new Intent(this, SettingsActivity.class)); 54 | return true; 55 | } 56 | 57 | return super.onOptionsItemSelected(item); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /PeriodicTable/src/main/java/com/frozendevs/periodictable/fragment/PropertiesFragment.java: -------------------------------------------------------------------------------- 1 | package com.frozendevs.periodictable.fragment; 2 | 3 | import android.os.Bundle; 4 | import android.support.v4.app.Fragment; 5 | import android.support.v7.widget.LinearLayoutManager; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | 10 | import com.frozendevs.periodictable.R; 11 | import com.frozendevs.periodictable.activity.PropertiesActivity; 12 | import com.frozendevs.periodictable.model.ElementProperties; 13 | import com.frozendevs.periodictable.model.adapter.PropertiesAdapter; 14 | import com.frozendevs.periodictable.view.RecyclerView; 15 | import com.frozendevs.periodictable.widget.DividerDecoration; 16 | 17 | public class PropertiesFragment extends Fragment { 18 | 19 | @Override 20 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 21 | Bundle savedInstanceState) { 22 | View layout = inflater.inflate(R.layout.properties_fragment, container, false); 23 | 24 | final RecyclerView recyclerView = (RecyclerView) layout.findViewById(R.id.properties_list); 25 | 26 | PropertiesAdapter adapter = new PropertiesAdapter(getActivity(), 27 | (ElementProperties) getArguments().get(PropertiesActivity.ARGUMENT_PROPERTIES)); 28 | recyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); 29 | recyclerView.setAdapter(adapter); 30 | recyclerView.addItemDecoration(new DividerDecoration(getActivity())); 31 | recyclerView.setOnHierarchyChangeListener(new ViewGroup.OnHierarchyChangeListener() { 32 | @Override 33 | public void onChildViewAdded(View parent, View child) { 34 | if (child.findViewById(R.id.tile_view) != null) { 35 | getActivity().supportStartPostponedEnterTransition(); 36 | 37 | recyclerView.setOnHierarchyChangeListener(null); 38 | } 39 | } 40 | 41 | @Override 42 | public void onChildViewRemoved(View parent, View child) { 43 | } 44 | }); 45 | 46 | getActivity().registerForContextMenu(recyclerView); 47 | 48 | return layout; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /PeriodicTable/src/main/res/layout/elements_list_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 22 | 23 | 31 | 32 | 40 | 41 | 42 | 43 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /PeriodicTable/src/main/java/com/frozendevs/periodictable/content/Database.java: -------------------------------------------------------------------------------- 1 | package com.frozendevs.periodictable.content; 2 | 3 | import android.content.Context; 4 | 5 | import com.frozendevs.periodictable.R; 6 | import com.google.gson.Gson; 7 | import com.google.gson.stream.JsonReader; 8 | 9 | import java.io.IOException; 10 | import java.io.InputStreamReader; 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | public class Database { 15 | private static InputStreamReader getInputStreamReader(Context context) { 16 | return new InputStreamReader(context.getResources().openRawResource(R.raw.database)); 17 | } 18 | 19 | public static List getAllElements(Context context, Class classOfT) { 20 | final List elements = new ArrayList<>(); 21 | 22 | final JsonReader reader = new JsonReader(getInputStreamReader(context)); 23 | 24 | try { 25 | reader.beginArray(); 26 | 27 | final Gson gson = new Gson(); 28 | 29 | while (reader.hasNext()) { 30 | elements.add(classOfT.cast(gson.fromJson(reader, classOfT))); 31 | } 32 | 33 | reader.endArray(); 34 | } catch (IOException e) { 35 | e.printStackTrace(); 36 | } finally { 37 | try { 38 | reader.close(); 39 | } catch (IOException e) { 40 | e.printStackTrace(); 41 | } 42 | } 43 | 44 | return elements; 45 | } 46 | 47 | public static T getElement(Context context, Class classOfT, int number) { 48 | T element = null; 49 | 50 | final JsonReader reader = new JsonReader(getInputStreamReader(context)); 51 | 52 | try { 53 | reader.beginArray(); 54 | 55 | final Gson gson = new Gson(); 56 | 57 | for (int i = 1; reader.hasNext(); i++) { 58 | if (i == number) { 59 | element = classOfT.cast(gson.fromJson(reader, classOfT)); 60 | 61 | break; 62 | } else { 63 | reader.skipValue(); 64 | } 65 | } 66 | } catch (IOException e) { 67 | e.printStackTrace(); 68 | } finally { 69 | try { 70 | reader.close(); 71 | } catch (IOException e) { 72 | e.printStackTrace(); 73 | } 74 | } 75 | 76 | return element; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /PeriodicTable/src/main/java/com/frozendevs/periodictable/widget/DividerDecoration.java: -------------------------------------------------------------------------------- 1 | package com.frozendevs.periodictable.widget; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.Canvas; 6 | import android.graphics.drawable.Drawable; 7 | import android.support.v4.view.ViewCompat; 8 | import android.support.v7.widget.RecyclerView; 9 | import android.view.View; 10 | 11 | import com.frozendevs.periodictable.model.adapter.PropertiesAdapter; 12 | 13 | public class DividerDecoration extends RecyclerView.ItemDecoration { 14 | 15 | private static final int[] ATTRS = {android.R.attr.listDivider}; 16 | 17 | private Drawable mDivider; 18 | 19 | public DividerDecoration(Context context) { 20 | TypedArray typedArray = context.obtainStyledAttributes(ATTRS); 21 | mDivider = typedArray.getDrawable(0); 22 | typedArray.recycle(); 23 | } 24 | 25 | @Override 26 | public void onDrawOver(Canvas canvas, RecyclerView parent, RecyclerView.State state) { 27 | final int left = parent.getPaddingLeft(); 28 | final int right = parent.getWidth() - parent.getPaddingRight(); 29 | 30 | final int childCount = parent.getChildCount(); 31 | for (int i = 0; i < childCount; i++) { 32 | final View child = parent.getChildAt(i); 33 | 34 | if (isDecorated(child, parent)) { 35 | View nextChild = null; 36 | if (i < childCount - 1) nextChild = parent.getChildAt(i + 1); 37 | if (nextChild != null && !isDecorated(nextChild, parent)) { 38 | continue; 39 | } 40 | final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child 41 | .getLayoutParams(); 42 | final int top = child.getBottom() + params.bottomMargin + 43 | (int) ViewCompat.getTranslationY(child); 44 | final int bottom = top + mDivider.getIntrinsicHeight(); 45 | mDivider.setBounds(left, top, right, bottom); 46 | mDivider.draw(canvas); 47 | } 48 | } 49 | } 50 | 51 | private boolean isDecorated(View view, RecyclerView parent) { 52 | RecyclerView.ViewHolder holder = parent.getChildViewHolder(view); 53 | 54 | return !(holder instanceof PropertiesAdapter.ViewHolder && 55 | holder.getItemViewType() == PropertiesAdapter.VIEW_TYPE_HEADER); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /PeriodicTable/src/main/assets/html/licenses.html: -------------------------------------------------------------------------------- 1 | 2 |

Notices for libraries:

3 |
    4 |
  • support-v4
  • 5 |
  • appcompat-v7
  • 6 |
7 |
 8 | Copyright (C) 2005-2013 The Android Open Source Project
 9 | 
10 | Licensed under the Apache License, Version 2.0 (the "License");
11 | you may not use this file except in compliance with the License.
12 | You may obtain a copy of the License at
13 | 
14 |     http://www.apache.org/licenses/LICENSE-2.0
15 | 
16 | Unless required by applicable law or agreed to in writing, software
17 | distributed under the License is distributed on an "AS IS" BASIS,
18 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19 | See the License for the specific language governing permissions and
20 | limitations under the License.
21 | 
22 |
    23 |
  • gson
  • 24 |
25 |
26 | Copyright (c) 2008-2009 Google Inc.
27 | 
28 | Licensed under the Apache License, Version 2.0 (the "License");
29 | you may not use this file except in compliance with the License.
30 | You may obtain a copy of the License at
31 | 
32 |     http://www.apache.org/licenses/LICENSE-2.0
33 | 
34 | Unless required by applicable law or agreed to in writing, software
35 | distributed under the License is distributed on an "AS IS" BASIS,
36 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
37 | See the License for the specific language governing permissions and
38 | limitations under the License.
39 | 
40 |

Notices for files:

41 |
    42 |
  • NotoSans-Regular.ttf
  • 43 |
44 |
45 | Copyright (c) 2013 Google Inc.
46 | 
47 | Licensed under the Apache License, Version 2.0 (the "License");
48 | you may not use this file except in compliance with the License.
49 | You may obtain a copy of the License at
50 | 
51 |     http://www.apache.org/licenses/LICENSE-2.0
52 | 
53 | Unless required by applicable law or agreed to in writing, software
54 | distributed under the License is distributed on an "AS IS" BASIS,
55 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
56 | See the License for the specific language governing permissions and
57 | limitations under the License.
58 | 
59 |
    60 |
  • database.json
  • 61 |
62 |
63 | This file uses material from the Wikipedia article "Periodic table", which is released under the Creative Commons Attribution-Share-Alike License 3.0.
64 | 
65 | -------------------------------------------------------------------------------- /PeriodicTable/src/main/res/layout/table_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 19 | 20 | 33 | 34 | 45 | 46 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /PeriodicTable/src/main/res/layout/isotope_list_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 22 | 23 | 31 | 32 | 39 | 40 | 48 | 49 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /PeriodicTable/src/main/java/com/frozendevs/periodictable/view/ExpandableIndicatorView.java: -------------------------------------------------------------------------------- 1 | package com.frozendevs.periodictable.view; 2 | 3 | import android.annotation.TargetApi; 4 | import android.content.Context; 5 | import android.content.res.Resources; 6 | import android.content.res.TypedArray; 7 | import android.graphics.drawable.StateListDrawable; 8 | import android.os.Build; 9 | import android.util.AttributeSet; 10 | import android.util.TypedValue; 11 | import android.widget.ImageView; 12 | 13 | import com.frozendevs.periodictable.R; 14 | 15 | public class ExpandableIndicatorView extends ImageView { 16 | private StateListDrawable mGroupIndicator; 17 | 18 | public ExpandableIndicatorView(Context context) { 19 | super(context); 20 | 21 | initExpandableIndicatorView(context); 22 | } 23 | 24 | public ExpandableIndicatorView(Context context, AttributeSet attrs) { 25 | super(context, attrs); 26 | 27 | initExpandableIndicatorView(context); 28 | } 29 | 30 | public ExpandableIndicatorView(Context context, AttributeSet attrs, int defStyleAttr) { 31 | super(context, attrs, defStyleAttr); 32 | 33 | initExpandableIndicatorView(context); 34 | } 35 | 36 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 37 | public ExpandableIndicatorView(Context context, AttributeSet attrs, int defStyleAttr, 38 | int defStyleRes) { 39 | super(context, attrs, defStyleAttr, defStyleRes); 40 | 41 | initExpandableIndicatorView(context); 42 | } 43 | 44 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 45 | private void initExpandableIndicatorView(Context context) { 46 | Resources.Theme theme = context.getTheme(); 47 | TypedValue typedValue = new TypedValue(); 48 | 49 | theme.resolveAttribute(android.R.attr.expandableListViewStyle, typedValue, true); 50 | 51 | TypedArray typedArray = theme.obtainStyledAttributes(typedValue.resourceId, 52 | new int[]{android.R.attr.groupIndicator, R.attr.colorControlHighlight}); 53 | 54 | mGroupIndicator = (StateListDrawable) typedArray.getDrawable(0); 55 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 56 | final int tintColor = typedArray.getColor(1, 0); 57 | 58 | if (tintColor != 0) { 59 | mGroupIndicator.setTint(tintColor); 60 | } 61 | } 62 | 63 | typedArray.recycle(); 64 | 65 | setStateExpanded(false); 66 | } 67 | 68 | public void setStateExpanded(boolean expanded) { 69 | mGroupIndicator.setState(expanded ? new int[]{android.R.attr.state_expanded} : null); 70 | 71 | setImageDrawable(mGroupIndicator.getCurrent()); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /PeriodicTable/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 13 | 14 | 15 | 16 | 17 | 25 | 26 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 61 | 62 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /PeriodicTable/src/main/java/com/frozendevs/periodictable/view/RecyclerView.java: -------------------------------------------------------------------------------- 1 | package com.frozendevs.periodictable.view; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.view.ContextMenu; 6 | import android.view.View; 7 | 8 | public class RecyclerView extends android.support.v7.widget.RecyclerView { 9 | private View mEmptyView; 10 | private RecyclerContextMenuInfo mContextMenuInfo; 11 | 12 | public class RecyclerContextMenuInfo implements ContextMenu.ContextMenuInfo { 13 | public View targetView; 14 | public int position; 15 | public long id; 16 | 17 | public RecyclerContextMenuInfo(View targetView, int position, long id) { 18 | this.targetView = targetView; 19 | this.position = position; 20 | this.id = id; 21 | } 22 | } 23 | 24 | private final android.support.v7.widget.RecyclerView.AdapterDataObserver observer = 25 | new android.support.v7.widget.RecyclerView.AdapterDataObserver() { 26 | @Override 27 | public void onChanged() { 28 | checkIfEmpty(); 29 | } 30 | 31 | @Override 32 | public void onItemRangeInserted(int positionStart, int itemCount) { 33 | checkIfEmpty(); 34 | } 35 | 36 | @Override 37 | public void onItemRangeRemoved(int positionStart, int itemCount) { 38 | checkIfEmpty(); 39 | } 40 | }; 41 | 42 | public RecyclerView(Context context) { 43 | super(context); 44 | } 45 | 46 | public RecyclerView(Context context, AttributeSet attrs) { 47 | super(context, attrs); 48 | } 49 | 50 | public RecyclerView(Context context, AttributeSet attrs, int defStyle) { 51 | super(context, attrs, defStyle); 52 | } 53 | 54 | private void checkIfEmpty() { 55 | if (mEmptyView != null && getAdapter() != null) { 56 | final boolean emptyViewVisible = getAdapter().getItemCount() == 0; 57 | 58 | mEmptyView.setVisibility(emptyViewVisible ? VISIBLE : GONE); 59 | 60 | setVisibility(emptyViewVisible ? GONE : VISIBLE); 61 | } 62 | } 63 | 64 | @Override 65 | public void setAdapter(android.support.v7.widget.RecyclerView.Adapter adapter) { 66 | final RecyclerView.Adapter oldAdapter = getAdapter(); 67 | 68 | if (oldAdapter != null) { 69 | oldAdapter.unregisterAdapterDataObserver(observer); 70 | } 71 | 72 | super.setAdapter(adapter); 73 | 74 | if (adapter != null) { 75 | adapter.registerAdapterDataObserver(observer); 76 | } 77 | 78 | checkIfEmpty(); 79 | } 80 | 81 | public void setEmptyView(View emptyView) { 82 | mEmptyView = emptyView; 83 | 84 | checkIfEmpty(); 85 | } 86 | 87 | @Override 88 | protected ContextMenu.ContextMenuInfo getContextMenuInfo() { 89 | return mContextMenuInfo; 90 | } 91 | 92 | @Override 93 | public boolean showContextMenuForChild(View originalView) { 94 | final int position = getChildAdapterPosition(originalView); 95 | 96 | if (position > NO_POSITION) { 97 | mContextMenuInfo = new RecyclerContextMenuInfo(originalView, position, 98 | getAdapter().getItemId(position)); 99 | 100 | return super.showContextMenuForChild(originalView); 101 | } 102 | 103 | return false; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /PeriodicTable/src/main/java/com/frozendevs/periodictable/fragment/IsotopesFragment.java: -------------------------------------------------------------------------------- 1 | package com.frozendevs.periodictable.fragment; 2 | 3 | import android.os.Bundle; 4 | import android.os.Parcelable; 5 | import android.support.v4.app.Fragment; 6 | import android.support.v7.widget.LinearLayoutManager; 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | 11 | import com.frozendevs.periodictable.R; 12 | import com.frozendevs.periodictable.activity.PropertiesActivity; 13 | import com.frozendevs.periodictable.model.ElementProperties; 14 | import com.frozendevs.periodictable.model.adapter.IsotopesAdapter; 15 | import com.frozendevs.periodictable.view.RecyclerView; 16 | import com.frozendevs.periodictable.widget.DividerDecoration; 17 | import com.h6ah4i.android.widget.advrecyclerview.animator.GeneralItemAnimator; 18 | import com.h6ah4i.android.widget.advrecyclerview.animator.RefactoredDefaultItemAnimator; 19 | import com.h6ah4i.android.widget.advrecyclerview.expandable.RecyclerViewExpandableItemManager; 20 | import com.h6ah4i.android.widget.advrecyclerview.utils.WrapperAdapterUtils; 21 | 22 | public class IsotopesFragment extends Fragment { 23 | private static final String SAVED_STATE_EXPANDABLE_ITEM_MANAGER = 24 | "RecyclerViewExpandableItemManager"; 25 | 26 | private RecyclerView.Adapter mWrappedAdapter; 27 | private RecyclerViewExpandableItemManager mRecyclerViewExpandableItemManager; 28 | 29 | @Override 30 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 31 | Bundle savedInstanceState) { 32 | View layout = inflater.inflate(R.layout.properties_fragment, container, false); 33 | 34 | final RecyclerView recyclerView = (RecyclerView) layout.findViewById(R.id.properties_list); 35 | 36 | final Parcelable savedState = (savedInstanceState != null) ? 37 | savedInstanceState.getParcelable(SAVED_STATE_EXPANDABLE_ITEM_MANAGER) : null; 38 | mRecyclerViewExpandableItemManager = new RecyclerViewExpandableItemManager(savedState); 39 | 40 | ElementProperties properties = (ElementProperties) getArguments().get( 41 | PropertiesActivity.ARGUMENT_PROPERTIES); 42 | 43 | IsotopesAdapter adapter = new IsotopesAdapter(getActivity(), properties.getIsotopes()); 44 | 45 | mWrappedAdapter = mRecyclerViewExpandableItemManager.createWrappedAdapter(adapter); 46 | 47 | GeneralItemAnimator animator = new RefactoredDefaultItemAnimator(); 48 | animator.setSupportsChangeAnimations(false); 49 | 50 | recyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); 51 | recyclerView.setAdapter(mWrappedAdapter); 52 | recyclerView.addItemDecoration(new DividerDecoration(getActivity())); 53 | recyclerView.setItemAnimator(animator); 54 | 55 | mRecyclerViewExpandableItemManager.attachRecyclerView(recyclerView); 56 | 57 | getActivity().registerForContextMenu(recyclerView); 58 | 59 | return layout; 60 | } 61 | 62 | @Override 63 | public void onDestroyView() { 64 | if (mRecyclerViewExpandableItemManager != null) { 65 | mRecyclerViewExpandableItemManager.release(); 66 | } 67 | 68 | if (mWrappedAdapter != null) { 69 | WrapperAdapterUtils.releaseAll(mWrappedAdapter); 70 | } 71 | 72 | super.onDestroyView(); 73 | } 74 | 75 | @Override 76 | public void onSaveInstanceState(Bundle outState) { 77 | super.onSaveInstanceState(outState); 78 | 79 | if (mRecyclerViewExpandableItemManager != null) { 80 | outState.putParcelable(SAVED_STATE_EXPANDABLE_ITEM_MANAGER, 81 | mRecyclerViewExpandableItemManager.getSavedState()); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /PeriodicTable/src/main/java/com/frozendevs/periodictable/widget/Zoomer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 The Android Open Source Project 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.frozendevs.periodictable.widget; 18 | 19 | import android.content.Context; 20 | import android.os.SystemClock; 21 | import android.view.animation.DecelerateInterpolator; 22 | import android.view.animation.Interpolator; 23 | 24 | /** 25 | * A simple class that animates double-touch zoom gestures. Functionally similar to a {@link 26 | * android.widget.Scroller}. 27 | */ 28 | public class Zoomer { 29 | /** 30 | * The interpolator, used for making zooms animate 'naturally.' 31 | */ 32 | private Interpolator mInterpolator; 33 | 34 | /** 35 | * The total animation duration for a zoom. 36 | */ 37 | private int mAnimationDurationMillis; 38 | 39 | /** 40 | * Whether or not the current zoom has finished. 41 | */ 42 | private boolean mFinished = true; 43 | 44 | /** 45 | * The current zoom value; computed by {@link #computeZoom()}. 46 | */ 47 | private float mCurrentZoom; 48 | 49 | /** 50 | * The time the zoom started, computed using {@link android.os.SystemClock#elapsedRealtime()}. 51 | */ 52 | private long mStartRTC; 53 | 54 | /** 55 | * The destination zoom factor. 56 | */ 57 | private float mEndZoom; 58 | 59 | public Zoomer(Context context) { 60 | mInterpolator = new DecelerateInterpolator(); 61 | mAnimationDurationMillis = context.getResources().getInteger( 62 | android.R.integer.config_shortAnimTime); 63 | } 64 | 65 | /** 66 | * Forces the zoom finished state to the given value. Unlike {@link #abortAnimation()}, the 67 | * current zoom value isn't set to the ending value. 68 | * 69 | * @see android.widget.Scroller#forceFinished(boolean) 70 | */ 71 | public void forceFinished(boolean finished) { 72 | mFinished = finished; 73 | } 74 | 75 | /** 76 | * Aborts the animation, setting the current zoom value to the ending value. 77 | * 78 | * @see android.widget.Scroller#abortAnimation() 79 | */ 80 | public void abortAnimation() { 81 | mFinished = true; 82 | mCurrentZoom = mEndZoom; 83 | } 84 | 85 | /** 86 | * Starts a zoom from 1.0 to (1.0 + endZoom). That is, to zoom from 100% to 125%, endZoom should 87 | * by 0.25f. 88 | * 89 | * @see android.widget.Scroller#startScroll(int, int, int, int) 90 | */ 91 | public void startZoom(float endZoom) { 92 | mStartRTC = SystemClock.elapsedRealtime(); 93 | mEndZoom = endZoom; 94 | 95 | mFinished = false; 96 | mCurrentZoom = 1f; 97 | } 98 | 99 | /** 100 | * Computes the current zoom level, returning true if the zoom is still active and false if the 101 | * zoom has finished. 102 | * 103 | * @see android.widget.Scroller#computeScrollOffset() 104 | */ 105 | public boolean computeZoom() { 106 | if (mFinished) { 107 | return false; 108 | } 109 | 110 | long tRTC = SystemClock.elapsedRealtime() - mStartRTC; 111 | if (tRTC >= mAnimationDurationMillis) { 112 | mFinished = true; 113 | mCurrentZoom = mEndZoom; 114 | return true; 115 | } 116 | 117 | float t = tRTC * 1f / mAnimationDurationMillis; 118 | mCurrentZoom = mEndZoom * mInterpolator.getInterpolation(t); 119 | return true; 120 | } 121 | 122 | /** 123 | * Returns the current zoom level. 124 | * 125 | * @see android.widget.Scroller#getCurrX() 126 | */ 127 | public float getCurrZoom() { 128 | return mCurrentZoom; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /PeriodicTable/src/main/java/com/frozendevs/periodictable/model/adapter/ElementsAdapter.java: -------------------------------------------------------------------------------- 1 | package com.frozendevs.periodictable.model.adapter; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.widget.TextView; 9 | 10 | import com.frozendevs.periodictable.R; 11 | import com.frozendevs.periodictable.activity.PropertiesActivity; 12 | import com.frozendevs.periodictable.model.ElementListItem; 13 | import com.frozendevs.periodictable.view.RecyclerView; 14 | 15 | import java.util.ArrayList; 16 | import java.util.Arrays; 17 | import java.util.List; 18 | import java.util.Locale; 19 | 20 | public class ElementsAdapter extends RecyclerView.Adapter { 21 | private ElementListItem[] mItems = new ElementListItem[0]; 22 | private List mFilteredItems = new ArrayList<>(); 23 | 24 | public class ViewHolder extends RecyclerView.ViewHolder implements 25 | View.OnClickListener { 26 | TextView mSymbolView, mNumberView, mNameView; 27 | int mNumber; 28 | 29 | public ViewHolder(View itemView) { 30 | super(itemView); 31 | 32 | mSymbolView = (TextView) itemView.findViewById(R.id.element_symbol); 33 | mNumberView = (TextView) itemView.findViewById(R.id.element_number); 34 | mNameView = (TextView) itemView.findViewById(R.id.element_name); 35 | 36 | itemView.setOnClickListener(this); 37 | } 38 | 39 | public void setName(String name) { 40 | mNameView.setText(name); 41 | } 42 | 43 | public void setNumber(int number) { 44 | mNumberView.setText(Integer.toString(mNumber = number)); 45 | } 46 | 47 | public void setSymbol(String symbol) { 48 | mSymbolView.setText(symbol); 49 | } 50 | 51 | @Override 52 | public void onClick(View view) { 53 | Intent intent = new Intent(view.getContext(), PropertiesActivity.class); 54 | intent.putExtra(PropertiesActivity.EXTRA_ATOMIC_NUMBER, mNumber); 55 | 56 | view.getContext().startActivity(intent); 57 | } 58 | } 59 | 60 | public ElementsAdapter() { 61 | setHasStableIds(true); 62 | } 63 | 64 | @Override 65 | public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 66 | return new ViewHolder(LayoutInflater.from(parent.getContext()).inflate( 67 | R.layout.elements_list_item, parent, false)); 68 | } 69 | 70 | @Override 71 | public void onBindViewHolder(ViewHolder holder, int position) { 72 | ElementListItem item = mFilteredItems.get(position); 73 | 74 | holder.setName(item.getName()); 75 | holder.setNumber(item.getNumber()); 76 | holder.setSymbol(item.getSymbol()); 77 | } 78 | 79 | @Override 80 | public int getItemCount() { 81 | return mFilteredItems.size(); 82 | } 83 | 84 | @Override 85 | public long getItemId(int i) { 86 | return mFilteredItems.get(i).hashCode(); 87 | } 88 | 89 | public void filter(Context context, String filter) { 90 | if (mItems.length > 0 && filter != null) { 91 | List filteredItems = new ArrayList<>(); 92 | 93 | Locale locale = context.getResources().getConfiguration().locale; 94 | 95 | int nextPos = 0; 96 | for (ElementListItem element : mItems) { 97 | if (element.getSymbol().toLowerCase(locale).equalsIgnoreCase(filter)) { 98 | filteredItems.add(0, element); 99 | 100 | nextPos += 1; 101 | } else if (element.getSymbol().toLowerCase(locale).startsWith(filter.toLowerCase( 102 | locale)) || String.valueOf(element.getNumber()).startsWith(filter)) { 103 | filteredItems.add(nextPos, element); 104 | 105 | nextPos += 1; 106 | } else if (element.getName().toLowerCase(locale).startsWith( 107 | filter.toLowerCase(locale))) { 108 | filteredItems.add(element); 109 | } 110 | } 111 | 112 | mFilteredItems = new ArrayList<>(filteredItems); 113 | 114 | notifyDataSetChanged(); 115 | } 116 | } 117 | 118 | public void clearFilter() { 119 | mFilteredItems = new ArrayList<>(Arrays.asList(mItems)); 120 | 121 | notifyDataSetChanged(); 122 | } 123 | 124 | public ElementListItem[] getItems() { 125 | return mItems; 126 | } 127 | 128 | public void setItems(List items) { 129 | setItems(items.toArray(new ElementListItem[items.size()])); 130 | } 131 | 132 | public void setItems(ElementListItem[] items) { 133 | mItems = items != null ? items.clone() : new ElementListItem[0]; 134 | 135 | mFilteredItems = new ArrayList<>(Arrays.asList(mItems)); 136 | 137 | notifyDataSetChanged(); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /PeriodicTable/src/main/java/com/frozendevs/periodictable/activity/SettingsActivity.java: -------------------------------------------------------------------------------- 1 | package com.frozendevs.periodictable.activity; 2 | 3 | import android.content.res.Configuration; 4 | import android.os.Bundle; 5 | import android.preference.Preference; 6 | import android.preference.PreferenceActivity; 7 | import android.support.annotation.LayoutRes; 8 | import android.support.annotation.Nullable; 9 | import android.support.v7.app.ActionBar; 10 | import android.support.v7.app.AlertDialog; 11 | import android.support.v7.app.AppCompatDelegate; 12 | import android.support.v7.widget.Toolbar; 13 | import android.view.MenuInflater; 14 | import android.view.MenuItem; 15 | import android.view.View; 16 | import android.view.ViewGroup; 17 | import android.webkit.WebView; 18 | 19 | import com.frozendevs.periodictable.R; 20 | 21 | public class SettingsActivity extends PreferenceActivity { 22 | private final static String ACTION_SETTINGS_ABOUT = "com.frozendevs.periodictable.SETTINGS_ABOUT"; 23 | 24 | private AppCompatDelegate mDelegate; 25 | 26 | @Override 27 | protected void onCreate(Bundle savedInstanceState) { 28 | getDelegate().installViewFactory(); 29 | getDelegate().onCreate(savedInstanceState); 30 | 31 | super.onCreate(savedInstanceState); 32 | 33 | String action = getIntent().getAction(); 34 | 35 | if (action != null && action.equals(ACTION_SETTINGS_ABOUT)) { 36 | addPreferencesFromResource(R.xml.settings_about); 37 | 38 | String versionName; 39 | try { 40 | versionName = getPackageManager().getPackageInfo(getPackageName(), 0).versionName; 41 | } catch (Exception e) { 42 | versionName = getString(R.string.preference_version_number_unknown); 43 | } 44 | findPreference("version").setSummary(versionName); 45 | 46 | findPreference("licences").setOnPreferenceClickListener( 47 | new Preference.OnPreferenceClickListener() { 48 | @Override 49 | public boolean onPreferenceClick(Preference preference) { 50 | WebView webView = new WebView(getApplicationContext()); 51 | webView.loadUrl("file:///android_asset/html/licenses.html"); 52 | 53 | AlertDialog dialog = new AlertDialog.Builder(SettingsActivity.this). 54 | create(); 55 | dialog.setTitle(R.string.preference_open_source_licences); 56 | dialog.setView(webView); 57 | dialog.show(); 58 | 59 | return true; 60 | } 61 | }); 62 | } else { 63 | addPreferencesFromResource(R.xml.settings); 64 | } 65 | } 66 | 67 | @Override 68 | protected void onPostCreate(Bundle savedInstanceState) { 69 | super.onPostCreate(savedInstanceState); 70 | 71 | getDelegate().onPostCreate(savedInstanceState); 72 | } 73 | 74 | public ActionBar getSupportActionBar() { 75 | return getDelegate().getSupportActionBar(); 76 | } 77 | 78 | public void setSupportActionBar(@Nullable Toolbar toolbar) { 79 | getDelegate().setSupportActionBar(toolbar); 80 | } 81 | 82 | @Override 83 | public MenuInflater getMenuInflater() { 84 | return getDelegate().getMenuInflater(); 85 | } 86 | 87 | @Override 88 | public void setContentView(@LayoutRes int layoutResID) { 89 | getDelegate().setContentView(layoutResID); 90 | } 91 | 92 | @Override 93 | public void setContentView(View view) { 94 | getDelegate().setContentView(view); 95 | } 96 | 97 | @Override 98 | public void setContentView(View view, ViewGroup.LayoutParams params) { 99 | getDelegate().setContentView(view, params); 100 | } 101 | 102 | @Override 103 | public void addContentView(View view, ViewGroup.LayoutParams params) { 104 | getDelegate().addContentView(view, params); 105 | } 106 | 107 | @Override 108 | protected void onPostResume() { 109 | super.onPostResume(); 110 | 111 | getDelegate().onPostResume(); 112 | } 113 | 114 | @Override 115 | protected void onTitleChanged(CharSequence title, int color) { 116 | super.onTitleChanged(title, color); 117 | 118 | getDelegate().setTitle(title); 119 | } 120 | 121 | @Override 122 | public void onConfigurationChanged(Configuration newConfig) { 123 | super.onConfigurationChanged(newConfig); 124 | 125 | getDelegate().onConfigurationChanged(newConfig); 126 | } 127 | 128 | @Override 129 | protected void onStop() { 130 | super.onStop(); 131 | 132 | getDelegate().onStop(); 133 | } 134 | 135 | @Override 136 | protected void onDestroy() { 137 | super.onDestroy(); 138 | 139 | getDelegate().onDestroy(); 140 | } 141 | 142 | public void invalidateOptionsMenu() { 143 | getDelegate().invalidateOptionsMenu(); 144 | } 145 | 146 | private AppCompatDelegate getDelegate() { 147 | if (mDelegate == null) { 148 | mDelegate = AppCompatDelegate.create(this, null); 149 | } 150 | 151 | return mDelegate; 152 | } 153 | 154 | @Override 155 | public boolean onOptionsItemSelected(MenuItem item) { 156 | switch (item.getItemId()) { 157 | case android.R.id.home: 158 | finish(); 159 | return true; 160 | } 161 | 162 | return super.onOptionsItemSelected(item); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /PeriodicTable/src/main/java/com/frozendevs/periodictable/fragment/ElementsFragment.java: -------------------------------------------------------------------------------- 1 | package com.frozendevs.periodictable.fragment; 2 | 3 | import android.os.Bundle; 4 | import android.support.v4.app.Fragment; 5 | import android.support.v4.app.LoaderManager; 6 | import android.support.v4.content.Loader; 7 | import android.support.v4.view.MenuItemCompat; 8 | import android.support.v4.view.ViewCompat; 9 | import android.support.v7.widget.LinearLayoutManager; 10 | import android.support.v7.widget.SearchView; 11 | import android.view.LayoutInflater; 12 | import android.view.Menu; 13 | import android.view.MenuInflater; 14 | import android.view.MenuItem; 15 | import android.view.View; 16 | import android.view.ViewGroup; 17 | 18 | import com.frozendevs.periodictable.R; 19 | import com.frozendevs.periodictable.content.AsyncTaskLoader; 20 | import com.frozendevs.periodictable.content.Database; 21 | import com.frozendevs.periodictable.model.ElementListItem; 22 | import com.frozendevs.periodictable.model.adapter.ElementsAdapter; 23 | import com.frozendevs.periodictable.view.RecyclerView; 24 | import com.frozendevs.periodictable.widget.DividerDecoration; 25 | 26 | import java.util.List; 27 | 28 | public class ElementsFragment extends Fragment implements 29 | LoaderManager.LoaderCallbacks> { 30 | private static final String STATE_LIST_ITEMS = "listItems"; 31 | private static final String STATE_SEARCH_QUERY = "searchQuery"; 32 | 33 | private ElementsAdapter mAdapter; 34 | private RecyclerView mRecyclerView; 35 | private View mEmptyView; 36 | 37 | private String mSearchQuery; 38 | 39 | @Override 40 | public void onCreate(Bundle savedInstanceState) { 41 | super.onCreate(savedInstanceState); 42 | 43 | setHasOptionsMenu(true); 44 | 45 | mAdapter = new ElementsAdapter(); 46 | 47 | if (savedInstanceState != null) { 48 | mSearchQuery = savedInstanceState.getString(STATE_SEARCH_QUERY); 49 | 50 | mAdapter.setItems((ElementListItem[]) savedInstanceState.getParcelableArray( 51 | STATE_LIST_ITEMS)); 52 | } 53 | } 54 | 55 | @Override 56 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 57 | Bundle savedInstanceState) { 58 | View rootView = inflater.inflate(R.layout.elements_list_fragment, container, false); 59 | 60 | mEmptyView = rootView.findViewById(R.id.empty_elements_list); 61 | 62 | mRecyclerView = (RecyclerView) rootView.findViewById(R.id.elements_list); 63 | mRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); 64 | mRecyclerView.setAdapter(mAdapter); 65 | mRecyclerView.setEmptyView(mAdapter.getItemCount() > 0 ? mEmptyView : 66 | rootView.findViewById(R.id.progress_bar)); 67 | mRecyclerView.addItemDecoration(new DividerDecoration(getActivity())); 68 | 69 | return rootView; 70 | } 71 | 72 | @Override 73 | public void onActivityCreated(Bundle savedInstanceState) { 74 | super.onActivityCreated(savedInstanceState); 75 | 76 | if (mAdapter.getItemCount() == 0) { 77 | getLoaderManager().initLoader(R.id.elements_list_loader, null, this); 78 | } 79 | } 80 | 81 | @Override 82 | public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 83 | inflater.inflate(R.menu.elements_menu, menu); 84 | 85 | MenuItem searchItem = menu.findItem(R.id.action_search); 86 | 87 | final SearchView searchView = (SearchView) MenuItemCompat.getActionView(searchItem); 88 | searchView.setQueryHint(getString(R.string.search_query_hint)); 89 | searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { 90 | @Override 91 | public boolean onQueryTextSubmit(String query) { 92 | mSearchQuery = query; 93 | 94 | searchView.clearFocus(); 95 | 96 | return true; 97 | } 98 | 99 | @Override 100 | public boolean onQueryTextChange(String newText) { 101 | if (ViewCompat.isLaidOut(searchView) && mSearchQuery != null) { 102 | String oldText = mSearchQuery; 103 | 104 | mSearchQuery = newText; 105 | 106 | if (!oldText.equals(newText)) { 107 | mAdapter.filter(getActivity(), newText); 108 | } 109 | } 110 | 111 | return true; 112 | } 113 | }); 114 | 115 | MenuItemCompat.setOnActionExpandListener(searchItem, 116 | new MenuItemCompat.OnActionExpandListener() { 117 | @Override 118 | public boolean onMenuItemActionExpand(MenuItem item) { 119 | if (mSearchQuery == null) { 120 | mSearchQuery = ""; 121 | } 122 | 123 | return true; 124 | } 125 | 126 | @Override 127 | public boolean onMenuItemActionCollapse(MenuItem item) { 128 | mSearchQuery = null; 129 | 130 | mAdapter.clearFilter(); 131 | 132 | return true; 133 | } 134 | }); 135 | 136 | if (mSearchQuery != null) { 137 | MenuItemCompat.expandActionView(searchItem); 138 | 139 | searchView.setQuery(mSearchQuery, false); 140 | 141 | mAdapter.filter(getActivity(), mSearchQuery); 142 | } 143 | 144 | super.onCreateOptionsMenu(menu, inflater); 145 | } 146 | 147 | 148 | 149 | @Override 150 | public Loader> onCreateLoader(int id, Bundle args) { 151 | return new AsyncTaskLoader>(getActivity()) { 152 | @Override 153 | public List loadInBackground() { 154 | return Database.getAllElements(getContext(), ElementListItem.class); 155 | } 156 | }; 157 | } 158 | 159 | @Override 160 | public void onLoadFinished(Loader> loader, List data) { 161 | mAdapter.setItems(data); 162 | 163 | mAdapter.notifyDataSetChanged(); 164 | 165 | mRecyclerView.setEmptyView(mEmptyView); 166 | } 167 | 168 | @Override 169 | public void onLoaderReset(Loader> loader) { 170 | } 171 | 172 | @Override 173 | public void onSaveInstanceState(Bundle outState) { 174 | super.onSaveInstanceState(outState); 175 | 176 | outState.putParcelableArray(STATE_LIST_ITEMS, mAdapter.getItems()); 177 | outState.putString(STATE_SEARCH_QUERY, mSearchQuery); 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /PeriodicTable/src/main/res/layout/properties_activity.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 14 | 15 | 18 | 19 | 27 | 28 | 33 | 34 | 41 | 42 | 48 | 49 | 50 | 51 | 63 | 64 | 67 | 68 | 77 | 78 | 88 | 89 | 99 | 100 | 110 | 111 | 121 | 122 | 123 | 124 | 125 | 126 | 132 | 133 | 140 | 141 | 142 | 143 | 151 | 152 | 153 | 154 | 155 | -------------------------------------------------------------------------------- /PeriodicTable/src/main/java/com/frozendevs/periodictable/fragment/TableFragment.java: -------------------------------------------------------------------------------- 1 | package com.frozendevs.periodictable.fragment; 2 | 3 | import android.content.Intent; 4 | import android.graphics.Bitmap; 5 | import android.os.Build; 6 | import android.os.Bundle; 7 | import android.support.annotation.Nullable; 8 | import android.support.v4.app.ActivityCompat; 9 | import android.support.v4.app.ActivityOptionsCompat; 10 | import android.support.v4.app.Fragment; 11 | import android.support.v4.app.LoaderManager; 12 | import android.support.v4.content.Loader; 13 | import android.support.v4.util.LruCache; 14 | import android.view.LayoutInflater; 15 | import android.view.View; 16 | import android.view.ViewGroup; 17 | 18 | import com.frozendevs.periodictable.PeriodicTableApplication; 19 | import com.frozendevs.periodictable.R; 20 | import com.frozendevs.periodictable.activity.PropertiesActivity; 21 | import com.frozendevs.periodictable.content.AsyncTaskLoader; 22 | import com.frozendevs.periodictable.content.Database; 23 | import com.frozendevs.periodictable.model.TableElementItem; 24 | import com.frozendevs.periodictable.model.TableItem; 25 | import com.frozendevs.periodictable.model.adapter.TableAdapter; 26 | import com.frozendevs.periodictable.view.PeriodicTableView; 27 | 28 | import java.util.List; 29 | 30 | public class TableFragment extends Fragment implements PeriodicTableView.OnItemClickListener, 31 | LoaderManager.LoaderCallbacks> { 32 | private static final String STATE_TABLE_ADAPTER = "tableAdapter"; 33 | 34 | private TableAdapter mAdapter; 35 | private PeriodicTableView mPeriodicTableView; 36 | private LruCache mBitmapCache = new LruCache<>(1); 37 | 38 | private class SharedElementCallback extends android.support.v4.app.SharedElementCallback { 39 | @Override 40 | public void onSharedElementStart(List sharedElementNames, List sharedElements, 41 | List sharedElementSnapshots) { 42 | super.onSharedElementStart(sharedElementNames, sharedElements, sharedElementSnapshots); 43 | 44 | View view = sharedElements.get(0); 45 | 46 | ViewGroup.LayoutParams layoutParams = view.getLayoutParams(); 47 | 48 | view.measure(View.MeasureSpec.makeMeasureSpec(layoutParams.width, 49 | View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec( 50 | layoutParams.height, View.MeasureSpec.EXACTLY)); 51 | view.layout(view.getLeft(), view.getTop(), 52 | view.getLeft() + view.getMeasuredWidth(), 53 | view.getTop() + view.getMeasuredHeight()); 54 | view.setPivotX(0f); 55 | view.setPivotY(0f); 56 | view.setScaleX(mPeriodicTableView.getZoom()); 57 | view.setScaleY(mPeriodicTableView.getZoom()); 58 | } 59 | 60 | @Override 61 | public void onSharedElementEnd(List sharedElementNames, List sharedElements, 62 | List sharedElementSnapshots) { 63 | super.onSharedElementEnd(sharedElementNames, sharedElements, sharedElementSnapshots); 64 | 65 | View view = sharedElements.get(0); 66 | view.setScaleX(1f); 67 | view.setScaleY(1f); 68 | } 69 | } 70 | 71 | private class OnAttachStateChangeListener implements View.OnAttachStateChangeListener { 72 | @Override 73 | public void onViewAttachedToWindow(View v) { 74 | } 75 | 76 | @Override 77 | public void onViewDetachedFromWindow(View v) { 78 | final View activeView = mPeriodicTableView.getActiveView(); 79 | 80 | if (activeView != null) { 81 | activeView.setAlpha(1f); 82 | } 83 | } 84 | } 85 | 86 | @Override 87 | public void onCreate(Bundle savedInstanceState) { 88 | super.onCreate(savedInstanceState); 89 | 90 | setRetainInstance(true); 91 | 92 | if (savedInstanceState != null) { 93 | mAdapter = savedInstanceState.getParcelable(STATE_TABLE_ADAPTER); 94 | } else { 95 | mAdapter = new TableAdapter(); 96 | } 97 | } 98 | 99 | @Override 100 | public void onActivityCreated(@Nullable Bundle savedInstanceState) { 101 | super.onActivityCreated(savedInstanceState); 102 | 103 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 104 | final PeriodicTableApplication application = (PeriodicTableApplication) 105 | getActivity().getApplication(); 106 | 107 | application.setSharedElementCallback(new SharedElementCallback()); 108 | 109 | application.setOnAttachStateChangeListener(new OnAttachStateChangeListener()); 110 | } 111 | 112 | if (mAdapter.isEmpty()) { 113 | getLoaderManager().initLoader(R.id.table_loader, null, this); 114 | } 115 | } 116 | 117 | @Override 118 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 119 | Bundle savedInstanceState) { 120 | View rootView = inflater.inflate(R.layout.table_fragment, container, false); 121 | 122 | mPeriodicTableView = (PeriodicTableView) rootView.findViewById(R.id.elements_table); 123 | mPeriodicTableView.setBitmapCache(mBitmapCache); 124 | mPeriodicTableView.setAdapter(mAdapter); 125 | mPeriodicTableView.setOnItemClickListener(this); 126 | mPeriodicTableView.setEmptyView(rootView.findViewById(R.id.progress_bar)); 127 | 128 | return rootView; 129 | } 130 | 131 | @Override 132 | public void onItemClick(PeriodicTableView parent, View view, int position) { 133 | TableItem item = mAdapter.getItem(position); 134 | 135 | if (item instanceof TableElementItem) { 136 | parent.setEnabled(false); 137 | 138 | Intent intent = new Intent(getActivity(), PropertiesActivity.class); 139 | intent.putExtra(PropertiesActivity.EXTRA_ATOMIC_NUMBER, 140 | ((TableElementItem) item).getNumber()); 141 | 142 | ActivityCompat.startActivity(getActivity(), intent, 143 | ActivityOptionsCompat.makeSceneTransitionAnimation(getActivity(), view, 144 | getString(R.string.transition_table_item)).toBundle()); 145 | } 146 | } 147 | 148 | @Override 149 | public void onResume() { 150 | if (mPeriodicTableView != null) { 151 | mPeriodicTableView.setEnabled(true); 152 | } 153 | 154 | super.onResume(); 155 | } 156 | 157 | @Override 158 | public Loader> onCreateLoader(int id, Bundle args) { 159 | return new AsyncTaskLoader>(getActivity()) { 160 | @Override 161 | public List loadInBackground() { 162 | return Database.getAllElements(getContext(), TableElementItem.class); 163 | } 164 | }; 165 | } 166 | 167 | @Override 168 | public void onLoadFinished(Loader> loader, 169 | List data) { 170 | mAdapter.setItems(getActivity(), data); 171 | 172 | mBitmapCache.resize(mAdapter.getCount()); 173 | 174 | mAdapter.notifyDataSetChanged(); 175 | } 176 | 177 | @Override 178 | public void onLoaderReset(Loader> loader) { 179 | } 180 | 181 | @Override 182 | public void onSaveInstanceState(Bundle outState) { 183 | super.onSaveInstanceState(outState); 184 | 185 | outState.putParcelable(STATE_TABLE_ADAPTER, mAdapter); 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /PeriodicTable/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Periodic Table 5 | Frozen Developers 6 | 7 | 8 | Elements 9 | Table 10 | PROPERTIES 11 | ISOTOPES 12 | 13 | 14 | Loading… 15 | 16 | 17 | Search 18 | Settings 19 | Wikipedia article 20 | 21 | Options 22 | Copy 23 | 24 | 25 | Name, symbol or Z 26 | 27 | 28 | No such element. 29 | 30 | 31 | General properties 32 | Physical properties 33 | Atomic properties 34 | Miscellanea 35 | 36 | 37 | Name 38 | Symbol 39 | Atomic number 40 | Standard atomic weight 41 | Group 42 | Period 43 | Block 44 | Category 45 | Electron configuration 46 | Electrons per shell 47 | Appearance 48 | Phase 49 | Density 50 | Liquid density at melting point 51 | Liquid density at boiling point 52 | Melting point 53 | Sublimation point 54 | Boiling point 55 | Triple point 56 | Critical point 57 | Heat of fusion 58 | Heat of vaporization 59 | Molar heat capacity 60 | Oxidation states 61 | Electronegativity 62 | Molar ionization energies 63 | Atomic radius 64 | Covalent radius 65 | Van der Waals radius 66 | Crystal structure 67 | Magnetic ordering 68 | Thermal conductivity 69 | Thermal expansion 70 | Thermal diffusivity 71 | Electrical resistivity 72 | Band gap 73 | Curie point 74 | Tensile strength 75 | Speed of sound 76 | Poisson ratio 77 | Young\'s modulus 78 | Shear modulus 79 | Bulk modulus 80 | Mohs hardness 81 | Vickers hardness 82 | Brinell hardness 83 | CAS Number 84 | 85 | 86 | X 87 | Aᵣ 88 | Z 89 | 90 | 91 | Half-life 92 | Decay modes 93 | Spin 94 | Natural abundance 95 | 96 | 97 | Unknown 98 | None 99 | Stable 100 | Trace 101 | 102 | 103 | Settings 104 | Main preferences 105 | About 106 | Special thanks 107 | 108 | Download images via Wi-Fi only 109 | To reduce carrier data charges, do not download images over mobile networks 110 | download_wifi_only 111 | 112 | 113 | About Periodic Table 114 | Author 115 | Version number 116 | Unknown 117 | Open source licences 118 | 119 | Samuel\'s Graphics 120 | Icon and Play Store banner design 121 | 122 | 123 | Actinides 124 | Alkali metals 125 | Alkaline earth metals 126 | Diatomic nonmetals 127 | Lanthanides 128 | Metalloids 129 | Noble gases 130 | Polyatomic nonmetals 131 | Post-transition metals 132 | Transition metals 133 | Unknown chemical properties 134 | 135 | 136 | table_item 137 | 138 | 139 | -------------------------------------------------------------------------------- /PeriodicTable/src/main/java/com/frozendevs/periodictable/model/ElementProperties.java: -------------------------------------------------------------------------------- 1 | package com.frozendevs.periodictable.model; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | 6 | public class ElementProperties extends TableElementItem { 7 | 8 | private String block, electronConfiguration, wikipediaLink, appearance, phase, density, 9 | liquidDensityAtMeltingPoint, liquidDensityAtBoilingPoint, meltingPoint, 10 | sublimationPoint, boilingPoint, triplePoint, criticalPoint, heatOfFusion, 11 | heatOfVaporization, molarHeatCapacity, oxidationStates, electronegativity, 12 | molarIonizationEnergies, atomicRadius, covalentRadius, vanDerWaalsRadius, 13 | crystalStructure, magneticOrdering, thermalConductivity, thermalExpansion, 14 | speedOfSound, youngsModulus, shearModulus, bulkModulus, mohsHardness, brinellHardness, 15 | electronsPerShell, thermalDiffusivity, electricalResistivity, bandGap, curiePoint, 16 | tensileStrength, poissonRatio, vickersHardness, casNumber, imageUrl; 17 | private Isotope[] isotopes; 18 | 19 | public static final Parcelable.Creator CREATOR 20 | = new Parcelable.Creator() { 21 | public ElementProperties createFromParcel(Parcel in) { 22 | return new ElementProperties(in); 23 | } 24 | 25 | public ElementProperties[] newArray(int size) { 26 | return new ElementProperties[size]; 27 | } 28 | }; 29 | 30 | protected ElementProperties(Parcel in) { 31 | super(in); 32 | 33 | block = in.readString(); 34 | electronConfiguration = in.readString(); 35 | wikipediaLink = in.readString(); 36 | appearance = in.readString(); 37 | phase = in.readString(); 38 | density = in.readString(); 39 | liquidDensityAtMeltingPoint = in.readString(); 40 | liquidDensityAtBoilingPoint = in.readString(); 41 | meltingPoint = in.readString(); 42 | sublimationPoint = in.readString(); 43 | boilingPoint = in.readString(); 44 | triplePoint = in.readString(); 45 | criticalPoint = in.readString(); 46 | heatOfFusion = in.readString(); 47 | heatOfVaporization = in.readString(); 48 | molarHeatCapacity = in.readString(); 49 | oxidationStates = in.readString(); 50 | electronegativity = in.readString(); 51 | molarIonizationEnergies = in.readString(); 52 | atomicRadius = in.readString(); 53 | covalentRadius = in.readString(); 54 | vanDerWaalsRadius = in.readString(); 55 | crystalStructure = in.readString(); 56 | magneticOrdering = in.readString(); 57 | thermalConductivity = in.readString(); 58 | thermalExpansion = in.readString(); 59 | speedOfSound = in.readString(); 60 | youngsModulus = in.readString(); 61 | shearModulus = in.readString(); 62 | bulkModulus = in.readString(); 63 | mohsHardness = in.readString(); 64 | brinellHardness = in.readString(); 65 | electronsPerShell = in.readString(); 66 | thermalDiffusivity = in.readString(); 67 | electricalResistivity = in.readString(); 68 | bandGap = in.readString(); 69 | curiePoint = in.readString(); 70 | tensileStrength = in.readString(); 71 | poissonRatio = in.readString(); 72 | vickersHardness = in.readString(); 73 | casNumber = in.readString(); 74 | imageUrl = in.readString(); 75 | isotopes = (Isotope[]) in.readParcelableArray(Isotope.class.getClassLoader()); 76 | } 77 | 78 | @Override 79 | public void writeToParcel(Parcel dest, int flags) { 80 | super.writeToParcel(dest, flags); 81 | 82 | dest.writeString(block); 83 | dest.writeString(electronConfiguration); 84 | dest.writeString(wikipediaLink); 85 | dest.writeString(appearance); 86 | dest.writeString(phase); 87 | dest.writeString(density); 88 | dest.writeString(liquidDensityAtMeltingPoint); 89 | dest.writeString(liquidDensityAtBoilingPoint); 90 | dest.writeString(meltingPoint); 91 | dest.writeString(sublimationPoint); 92 | dest.writeString(boilingPoint); 93 | dest.writeString(triplePoint); 94 | dest.writeString(criticalPoint); 95 | dest.writeString(heatOfFusion); 96 | dest.writeString(heatOfVaporization); 97 | dest.writeString(molarHeatCapacity); 98 | dest.writeString(oxidationStates); 99 | dest.writeString(electronegativity); 100 | dest.writeString(molarIonizationEnergies); 101 | dest.writeString(atomicRadius); 102 | dest.writeString(covalentRadius); 103 | dest.writeString(vanDerWaalsRadius); 104 | dest.writeString(crystalStructure); 105 | dest.writeString(magneticOrdering); 106 | dest.writeString(thermalConductivity); 107 | dest.writeString(thermalExpansion); 108 | dest.writeString(speedOfSound); 109 | dest.writeString(youngsModulus); 110 | dest.writeString(shearModulus); 111 | dest.writeString(bulkModulus); 112 | dest.writeString(mohsHardness); 113 | dest.writeString(brinellHardness); 114 | dest.writeString(electronsPerShell); 115 | dest.writeString(thermalDiffusivity); 116 | dest.writeString(electricalResistivity); 117 | dest.writeString(bandGap); 118 | dest.writeString(curiePoint); 119 | dest.writeString(tensileStrength); 120 | dest.writeString(poissonRatio); 121 | dest.writeString(vickersHardness); 122 | dest.writeString(casNumber); 123 | dest.writeString(imageUrl); 124 | dest.writeParcelableArray(isotopes, 0); 125 | } 126 | 127 | public String getBlock() { 128 | return block; 129 | } 130 | 131 | public String getElectronConfiguration() { 132 | return electronConfiguration; 133 | } 134 | 135 | public String getWikipediaLink() { 136 | return wikipediaLink; 137 | } 138 | 139 | public String getAppearance() { 140 | return appearance; 141 | } 142 | 143 | public String getPhase() { 144 | return phase; 145 | } 146 | 147 | public String getDensity() { 148 | return density; 149 | } 150 | 151 | public String getLiquidDensityAtMeltingPoint() { 152 | return liquidDensityAtMeltingPoint; 153 | } 154 | 155 | public String getLiquidDensityAtBoilingPoint() { 156 | return liquidDensityAtBoilingPoint; 157 | } 158 | 159 | public String getMeltingPoint() { 160 | return meltingPoint; 161 | } 162 | 163 | public String getSublimationPoint() { 164 | return sublimationPoint; 165 | } 166 | 167 | public String getBoilingPoint() { 168 | return boilingPoint; 169 | } 170 | 171 | public String getTriplePoint() { 172 | return triplePoint; 173 | } 174 | 175 | public String getCriticalPoint() { 176 | return criticalPoint; 177 | } 178 | 179 | public String getHeatOfFusion() { 180 | return heatOfFusion; 181 | } 182 | 183 | public String getHeatOfVaporization() { 184 | return heatOfVaporization; 185 | } 186 | 187 | public String getMolarHeatCapacity() { 188 | return molarHeatCapacity; 189 | } 190 | 191 | public String getOxidationStates() { 192 | return oxidationStates; 193 | } 194 | 195 | public String getElectronegativity() { 196 | return electronegativity; 197 | } 198 | 199 | public Isotope[] getIsotopes() { 200 | return isotopes; 201 | } 202 | 203 | public String getMolarIonizationEnergies() { 204 | return molarIonizationEnergies; 205 | } 206 | 207 | public String getAtomicRadius() { 208 | return atomicRadius; 209 | } 210 | 211 | public String getCovalentRadius() { 212 | return covalentRadius; 213 | } 214 | 215 | public String getVanDerWaalsRadius() { 216 | return vanDerWaalsRadius; 217 | } 218 | 219 | public String getCrystalStructure() { 220 | return crystalStructure; 221 | } 222 | 223 | public String getMagneticOrdering() { 224 | return magneticOrdering; 225 | } 226 | 227 | public String getThermalConductivity() { 228 | return thermalConductivity; 229 | } 230 | 231 | public String getThermalExpansion() { 232 | return thermalExpansion; 233 | } 234 | 235 | public String getThermalDiffusivity() { 236 | return thermalDiffusivity; 237 | } 238 | 239 | public String getSpeedOfSound() { 240 | return speedOfSound; 241 | } 242 | 243 | public String getYoungsModulus() { 244 | return youngsModulus; 245 | } 246 | 247 | public String getShearModulus() { 248 | return shearModulus; 249 | } 250 | 251 | public String getBulkModulus() { 252 | return bulkModulus; 253 | } 254 | 255 | public String getMohsHardness() { 256 | return mohsHardness; 257 | } 258 | 259 | public String getBrinellHardness() { 260 | return brinellHardness; 261 | } 262 | 263 | public String getElectronsPerShell() { 264 | return electronsPerShell; 265 | } 266 | 267 | public String getElectricalResistivity() { 268 | return electricalResistivity; 269 | } 270 | 271 | public String getBandGap() { 272 | return bandGap; 273 | } 274 | 275 | public String getCuriePoint() { 276 | return curiePoint; 277 | } 278 | 279 | public String getTensileStrength() { 280 | return tensileStrength; 281 | } 282 | 283 | public String getPoissonRatio() { 284 | return poissonRatio; 285 | } 286 | 287 | public String getVickersHardness() { 288 | return vickersHardness; 289 | } 290 | 291 | public String getCasNumber() { 292 | return casNumber; 293 | } 294 | 295 | public String getImageUrl() { 296 | return imageUrl; 297 | } 298 | } 299 | -------------------------------------------------------------------------------- /PeriodicTable/src/main/java/com/frozendevs/periodictable/activity/PropertiesActivity.java: -------------------------------------------------------------------------------- 1 | package com.frozendevs.periodictable.activity; 2 | 3 | import android.content.ClipData; 4 | import android.content.ClipboardManager; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | import android.content.SharedPreferences; 8 | import android.graphics.Typeface; 9 | import android.net.ConnectivityManager; 10 | import android.net.NetworkInfo; 11 | import android.net.Uri; 12 | import android.os.Build; 13 | import android.os.Bundle; 14 | import android.preference.PreferenceManager; 15 | import android.support.design.widget.CollapsingToolbarLayout; 16 | import android.support.design.widget.TabLayout; 17 | import android.support.v4.app.SharedElementCallback; 18 | import android.support.v4.view.ViewPager; 19 | import android.support.v7.app.AppCompatActivity; 20 | import android.support.v7.widget.Toolbar; 21 | import android.view.ContextMenu; 22 | import android.view.Menu; 23 | import android.view.MenuItem; 24 | import android.view.View; 25 | import android.view.ViewGroup; 26 | import android.widget.ImageView; 27 | import android.widget.TextView; 28 | 29 | import com.frozendevs.periodictable.PeriodicTableApplication; 30 | import com.frozendevs.periodictable.R; 31 | import com.frozendevs.periodictable.fragment.IsotopesFragment; 32 | import com.frozendevs.periodictable.fragment.PropertiesFragment; 33 | import com.frozendevs.periodictable.content.Database; 34 | import com.frozendevs.periodictable.model.ElementProperties; 35 | import com.frozendevs.periodictable.model.adapter.PropertiesAdapter; 36 | import com.frozendevs.periodictable.model.adapter.TableAdapter; 37 | import com.frozendevs.periodictable.model.adapter.ViewPagerAdapter; 38 | import com.frozendevs.periodictable.view.RecyclerView; 39 | import com.squareup.picasso.Callback; 40 | import com.squareup.picasso.NetworkPolicy; 41 | import com.squareup.picasso.Picasso; 42 | import com.squareup.picasso.RequestCreator; 43 | 44 | public class PropertiesActivity extends AppCompatActivity { 45 | 46 | public static final String EXTRA_ATOMIC_NUMBER = "com.frozendevs.periodictable.AtomicNumber"; 47 | 48 | public static final String ARGUMENT_PROPERTIES = "properties"; 49 | 50 | private static final String STATE_ELEMENT_PROPERTIES = "elementProperties"; 51 | 52 | private ElementProperties mElementProperties; 53 | 54 | @Override 55 | protected void onCreate(Bundle savedInstanceState) { 56 | super.onCreate(savedInstanceState); 57 | 58 | setContentView(R.layout.properties_activity); 59 | 60 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 61 | final PeriodicTableApplication application = (PeriodicTableApplication) getApplication(); 62 | 63 | final SharedElementCallback callback = application.getSharedElementCallback(); 64 | 65 | if (callback != null) { 66 | setEnterSharedElementCallback(callback); 67 | } 68 | 69 | /* 70 | * Work around shared view alpha state not being restored on exit transition finished. 71 | */ 72 | final View.OnAttachStateChangeListener listener = 73 | application.getOnAttachStateChangeListener(); 74 | 75 | if (listener != null) { 76 | getWindow().getDecorView().addOnAttachStateChangeListener(listener); 77 | } 78 | } 79 | 80 | if (savedInstanceState == null || (mElementProperties = savedInstanceState.getParcelable( 81 | STATE_ELEMENT_PROPERTIES)) == null) { 82 | mElementProperties = Database.getElement(this, ElementProperties.class, 83 | getIntent().getIntExtra(EXTRA_ATOMIC_NUMBER, 1)); 84 | } 85 | 86 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); 87 | 88 | setSupportActionBar(toolbar); 89 | 90 | getSupportActionBar().setDisplayHomeAsUpEnabled(true); 91 | 92 | CollapsingToolbarLayout collapsingToolbar = (CollapsingToolbarLayout) findViewById( 93 | R.id.collapsing_toolbar); 94 | collapsingToolbar.setTitle(mElementProperties.getName()); 95 | 96 | Bundle bundle = new Bundle(); 97 | bundle.putParcelable(ARGUMENT_PROPERTIES, mElementProperties); 98 | 99 | ViewPagerAdapter pagerAdapter = new ViewPagerAdapter(this); 100 | pagerAdapter.addPage(R.string.fragment_title_properties, PropertiesFragment.class, bundle); 101 | pagerAdapter.addPage(R.string.fragment_title_isotopes, IsotopesFragment.class, bundle); 102 | 103 | ViewPager viewPager = (ViewPager) findViewById(R.id.pager); 104 | viewPager.setAdapter(pagerAdapter); 105 | 106 | TabLayout tabLayout = (TabLayout) findViewById(R.id.tabs); 107 | tabLayout.setupWithViewPager(viewPager); 108 | 109 | Typeface typeface = Typeface.createFromAsset(getAssets(), "fonts/NotoSans-Regular.ttf"); 110 | 111 | TableAdapter tableAdapter = new TableAdapter(); 112 | 113 | View tileView = findViewById(R.id.tile_view); 114 | tableAdapter.getView(mElementProperties, tileView, (ViewGroup) tileView.getParent()); 115 | tileView.setClickable(false); 116 | 117 | TextView configuration = (TextView) findViewById(R.id.element_electron_configuration); 118 | configuration.setText(PropertiesAdapter.formatProperty(this, 119 | mElementProperties.getElectronConfiguration())); 120 | configuration.setTypeface(typeface); 121 | 122 | TextView shells = (TextView) findViewById(R.id.element_electrons_per_shell); 123 | shells.setText(PropertiesAdapter.formatProperty(this, 124 | mElementProperties.getElectronsPerShell())); 125 | shells.setTypeface(typeface); 126 | 127 | TextView electronegativity = (TextView) findViewById(R.id.element_electronegativity); 128 | electronegativity.setText(PropertiesAdapter.formatProperty(this, 129 | mElementProperties.getElectronegativity())); 130 | electronegativity.setTypeface(typeface); 131 | 132 | TextView oxidationStates = (TextView) findViewById(R.id.element_oxidation_states); 133 | oxidationStates.setText(PropertiesAdapter.formatProperty(this, 134 | mElementProperties.getOxidationStates())); 135 | oxidationStates.setTypeface(typeface); 136 | 137 | String imageUrl = mElementProperties.getImageUrl(); 138 | 139 | final ImageView backdrop = (ImageView) findViewById(R.id.backdrop); 140 | 141 | if (imageUrl.equals("")) { 142 | backdrop.setImageResource(R.drawable.backdrop); 143 | } else { 144 | final ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService( 145 | Context.CONNECTIVITY_SERVICE); 146 | final NetworkInfo networkInfo = connectivityManager.getNetworkInfo( 147 | ConnectivityManager.TYPE_MOBILE); 148 | 149 | SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); 150 | 151 | String downloadWifiOnlyKey = getString(R.string.preferences_download_wifi_only_key); 152 | 153 | RequestCreator requestCreator = Picasso.with(this).load(imageUrl); 154 | if (networkInfo != null && (networkInfo.getState() == NetworkInfo.State.CONNECTED || 155 | networkInfo.getState() == NetworkInfo.State.CONNECTING) && 156 | preferences.getBoolean(downloadWifiOnlyKey, false)) { 157 | requestCreator.networkPolicy(NetworkPolicy.OFFLINE); 158 | } 159 | requestCreator.into(backdrop, new Callback() { 160 | @Override 161 | public void onSuccess() { 162 | } 163 | 164 | @Override 165 | public void onError() { 166 | backdrop.setImageResource(R.drawable.backdrop); 167 | } 168 | }); 169 | } 170 | } 171 | 172 | @Override 173 | public boolean onCreateOptionsMenu(Menu menu) { 174 | getMenuInflater().inflate(R.menu.properties_action_menu, menu); 175 | 176 | return true; 177 | } 178 | 179 | @Override 180 | public boolean onOptionsItemSelected(MenuItem item) { 181 | switch (item.getItemId()) { 182 | case R.id.action_wiki: 183 | startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse( 184 | mElementProperties.getWikipediaLink()))); 185 | return true; 186 | 187 | case android.R.id.home: 188 | supportFinishAfterTransition(); 189 | return true; 190 | } 191 | 192 | return super.onOptionsItemSelected(item); 193 | } 194 | 195 | @Override 196 | public void onCreateContextMenu(ContextMenu menu, View view, 197 | ContextMenu.ContextMenuInfo menuInfo) { 198 | getMenuInflater().inflate(R.menu.properties_context_menu, menu); 199 | menu.setHeaderTitle(R.string.context_title_options); 200 | 201 | super.onCreateContextMenu(menu, view, menuInfo); 202 | } 203 | 204 | @Override 205 | public boolean onContextItemSelected(MenuItem item) { 206 | String propertyName, propertyValue; 207 | 208 | View view = ((RecyclerView.RecyclerContextMenuInfo) item.getMenuInfo()).targetView; 209 | 210 | TextView symbol = (TextView) view.findViewById(R.id.property_symbol); 211 | 212 | if (symbol != null) { 213 | propertyName = getString(R.string.property_symbol); 214 | propertyValue = (String) symbol.getText(); 215 | } else { 216 | propertyName = (String) ((TextView) view.findViewById(R.id.property_name)).getText(); 217 | propertyValue = (String) ((TextView) view.findViewById(R.id.property_value)).getText(); 218 | } 219 | 220 | switch (item.getItemId()) { 221 | case R.id.context_copy: 222 | ((ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE)). 223 | setPrimaryClip(ClipData.newPlainText(propertyName, propertyValue)); 224 | return true; 225 | } 226 | 227 | return super.onContextItemSelected(item); 228 | } 229 | 230 | @Override 231 | protected void onSaveInstanceState(Bundle outState) { 232 | super.onSaveInstanceState(outState); 233 | 234 | outState.putParcelable(STATE_ELEMENT_PROPERTIES, mElementProperties); 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /PeriodicTable/src/main/java/com/frozendevs/periodictable/model/adapter/PropertiesAdapter.java: -------------------------------------------------------------------------------- 1 | package com.frozendevs.periodictable.model.adapter; 2 | 3 | import android.content.Context; 4 | import android.graphics.Typeface; 5 | import android.view.ContextMenu; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.TextView; 10 | 11 | import com.frozendevs.periodictable.R; 12 | import com.frozendevs.periodictable.model.ElementProperties; 13 | import com.frozendevs.periodictable.view.RecyclerView; 14 | 15 | public class PropertiesAdapter extends RecyclerView.Adapter { 16 | 17 | public static final int VIEW_TYPE_HEADER = 0; 18 | public static final int VIEW_TYPE_ITEM = 1; 19 | 20 | private static final int[] CATEGORIES = { 21 | R.string.category_diatomic_nonmetals, 22 | R.string.category_noble_gases, 23 | R.string.category_alkali_metals, 24 | R.string.category_alkaline_earth_metals, 25 | R.string.category_metalloids, 26 | R.string.category_polyatomic_nonmetals, 27 | R.string.category_other_metals, 28 | R.string.category_transition_metals, 29 | R.string.category_lanthanides, 30 | R.string.category_actinides, 31 | R.string.category_unknown 32 | }; 33 | 34 | private Context mContext; 35 | private Typeface mTypeface; 36 | private Property[] mProperties = new Property[0]; 37 | 38 | private class Property { 39 | String mName = "", mValue; 40 | 41 | Property(int name) { 42 | mName = mContext.getString(name); 43 | } 44 | 45 | Property(int name, String value) { 46 | this(name); 47 | 48 | mValue = formatProperty(mContext, value); 49 | } 50 | 51 | Property(int name, int value) { 52 | this(name, String.valueOf(value)); 53 | } 54 | 55 | String getName() { 56 | return mName; 57 | } 58 | 59 | String getValue() { 60 | return mValue; 61 | } 62 | } 63 | 64 | public class ViewHolder extends RecyclerView.ViewHolder implements 65 | View.OnClickListener, View.OnCreateContextMenuListener { 66 | TextView mName, mValue; 67 | 68 | public ViewHolder(View itemView) { 69 | super(itemView); 70 | 71 | mName = (TextView) itemView.findViewById(R.id.property_name); 72 | mValue = (TextView) itemView.findViewById(R.id.property_value); 73 | 74 | if (mValue != null) { 75 | mValue.setTypeface(mTypeface); 76 | 77 | itemView.setOnClickListener(this); 78 | itemView.setOnCreateContextMenuListener(this); 79 | } 80 | } 81 | 82 | public void setName(String name) { 83 | mName.setText(name); 84 | } 85 | 86 | public void setValue(String value) { 87 | mValue.setText(value); 88 | } 89 | 90 | @Override 91 | public void onClick(View view) { 92 | } 93 | 94 | @Override 95 | public void onCreateContextMenu(ContextMenu menu, View view, 96 | ContextMenu.ContextMenuInfo menuInfo) { 97 | } 98 | } 99 | 100 | public PropertiesAdapter(Context context, ElementProperties properties) { 101 | mContext = context; 102 | 103 | mTypeface = Typeface.createFromAsset(context.getAssets(), "fonts/NotoSans-Regular.ttf"); 104 | 105 | mProperties = new Property[]{ 106 | new Property(R.string.properties_header_general), 107 | new Property(R.string.property_symbol, properties.getSymbol()), 108 | new Property(R.string.property_atomic_number, properties.getNumber()), 109 | new Property(R.string.property_weight, properties.getStandardAtomicWeight()), 110 | new Property(R.string.property_group, properties.getGroup()), 111 | new Property(R.string.property_period, properties.getPeriod()), 112 | new Property(R.string.property_block, properties.getBlock()), 113 | new Property(R.string.property_category, mContext.getString( 114 | CATEGORIES[properties.getCategory()])), 115 | new Property(R.string.property_electron_configuration, 116 | properties.getElectronConfiguration()), 117 | new Property(R.string.property_electrons_per_shell, 118 | properties.getElectronsPerShell()), 119 | new Property(R.string.properties_header_physical), 120 | new Property(R.string.property_appearance, properties.getAppearance()), 121 | new Property(R.string.property_phase, properties.getPhase()), 122 | new Property(R.string.property_density, properties.getDensity()), 123 | new Property(R.string.property_liquid_density_at_mp, 124 | properties.getLiquidDensityAtMeltingPoint()), 125 | new Property(R.string.property_liquid_density_at_bp, 126 | properties.getLiquidDensityAtBoilingPoint()), 127 | new Property(R.string.property_melting_point, properties.getMeltingPoint()), 128 | new Property(R.string.property_sublimation_point, 129 | properties.getSublimationPoint()), 130 | new Property(R.string.property_boiling_point, properties.getBoilingPoint()), 131 | new Property(R.string.property_triple_point, properties.getTriplePoint()), 132 | new Property(R.string.property_critical_point, properties.getCriticalPoint()), 133 | new Property(R.string.property_heat_of_fusion, properties.getHeatOfFusion()), 134 | new Property(R.string.property_heat_of_vaporization, 135 | properties.getHeatOfVaporization()), 136 | new Property(R.string.property_molar_heat_capacity, 137 | properties.getMolarHeatCapacity()), 138 | new Property(R.string.properties_header_atomic), 139 | new Property(R.string.property_oxidation_states, 140 | properties.getOxidationStates()), 141 | new Property(R.string.property_electronegativity, 142 | properties.getElectronegativity()), 143 | new Property(R.string.property_molar_ionization_energies, 144 | properties.getMolarIonizationEnergies()), 145 | new Property(R.string.property_atomic_radius, properties.getAtomicRadius()), 146 | new Property(R.string.property_covalent_radius, 147 | properties.getCovalentRadius()), 148 | new Property(R.string.property_van_der_waals_radius, 149 | properties.getVanDerWaalsRadius()), 150 | new Property(R.string.properties_header_miscellanea), 151 | new Property(R.string.property_crystal_structure, 152 | properties.getCrystalStructure()), 153 | new Property(R.string.property_magnetic_ordering, 154 | properties.getMagneticOrdering()), 155 | new Property(R.string.property_thermal_conductivity, 156 | properties.getThermalConductivity()), 157 | new Property(R.string.property_thermal_expansion, 158 | properties.getThermalExpansion()), 159 | new Property(R.string.property_thermal_diffusivity, 160 | properties.getThermalDiffusivity()), 161 | new Property(R.string.property_electrical_resistivity, 162 | properties.getElectricalResistivity()), 163 | new Property(R.string.property_band_gap, properties.getBandGap()), 164 | new Property(R.string.property_curie_point, properties.getCuriePoint()), 165 | new Property(R.string.property_tensile_strength, 166 | properties.getTensileStrength()), 167 | new Property(R.string.property_speed_of_sound, properties.getSpeedOfSound()), 168 | new Property(R.string.property_poisson_ratio, properties.getPoissonRatio()), 169 | new Property(R.string.property_youngs_modulus, properties.getYoungsModulus()), 170 | new Property(R.string.property_shear_modulus, properties.getShearModulus()), 171 | new Property(R.string.property_bulk_modulus, properties.getBulkModulus()), 172 | new Property(R.string.property_mohs_hardness, properties.getMohsHardness()), 173 | new Property(R.string.property_vickers_hardness, 174 | properties.getVickersHardness()), 175 | new Property(R.string.property_brinell_hardness, 176 | properties.getBrinellHardness()), 177 | new Property(R.string.property_cas_number, properties.getCasNumber()) 178 | }; 179 | } 180 | 181 | @Override 182 | public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 183 | return new ViewHolder(LayoutInflater.from(mContext).inflate(viewType == VIEW_TYPE_HEADER 184 | ? R.layout.properties_list_header : R.layout.properties_list_item, parent, false)); 185 | } 186 | 187 | @Override 188 | public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { 189 | Property property = mProperties[position]; 190 | 191 | ((ViewHolder) holder).setName(property.getName()); 192 | 193 | if (getItemViewType(position) == VIEW_TYPE_ITEM) { 194 | ((ViewHolder) holder).setValue(property.getValue()); 195 | } 196 | } 197 | 198 | @Override 199 | public int getItemCount() { 200 | return mProperties.length; 201 | } 202 | 203 | @Override 204 | public int getItemViewType(int position) { 205 | return mProperties[position].getValue() == null ? VIEW_TYPE_HEADER : VIEW_TYPE_ITEM; 206 | } 207 | 208 | public static String formatProperty(Context context, String property) { 209 | switch (property) { 210 | case "": 211 | return context.getString(R.string.property_value_unknown); 212 | 213 | case "-": 214 | return context.getString(R.string.property_value_none); 215 | } 216 | 217 | return property; 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /PeriodicTable/src/main/java/com/frozendevs/periodictable/model/adapter/TableAdapter.java: -------------------------------------------------------------------------------- 1 | package com.frozendevs.periodictable.model.adapter; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | import android.graphics.Typeface; 6 | import android.os.Parcel; 7 | import android.os.Parcelable; 8 | import android.view.LayoutInflater; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | import android.widget.ImageView; 12 | import android.widget.TextView; 13 | 14 | import com.frozendevs.periodictable.R; 15 | import com.frozendevs.periodictable.model.TableElementItem; 16 | import com.frozendevs.periodictable.model.TableItem; 17 | import com.frozendevs.periodictable.model.TableTextItem; 18 | import com.frozendevs.periodictable.view.PeriodicTableView; 19 | 20 | import java.math.BigDecimal; 21 | import java.math.RoundingMode; 22 | import java.util.HashMap; 23 | import java.util.List; 24 | import java.util.Map; 25 | 26 | public class TableAdapter extends PeriodicTableView.Adapter implements Parcelable { 27 | 28 | private enum ViewType { 29 | ITEM, 30 | TEXT 31 | } 32 | 33 | private Typeface mTypeface; 34 | private int mGroupsCount; 35 | private int mPeriodsCount; 36 | private Map mItems = new HashMap<>(); 37 | 38 | private static final int[] COLORS = { 39 | R.color.category_diatomic_nonmetals_bg, 40 | R.color.category_noble_gases_bg, 41 | R.color.category_alkali_metals_bg, 42 | R.color.category_alkaline_earth_metals_bg, 43 | R.color.category_metalloids_bg, 44 | R.color.category_polyatomic_nonmetals_bg, 45 | R.color.category_other_metals_bg, 46 | R.color.category_transition_metals_bg, 47 | R.color.category_lanthanides_bg, 48 | R.color.category_actinides_bg, 49 | R.color.category_unknown_bg 50 | }; 51 | 52 | private static final Object[][] TEXT_ITEMS = { 53 | {4, R.string.category_actinides, 9}, 54 | {5, R.string.category_alkali_metals, 2}, 55 | {6, R.string.category_alkaline_earth_metals, 3}, 56 | {7, R.string.category_diatomic_nonmetals, 0}, 57 | {8, R.string.category_lanthanides, 8}, 58 | {9, R.string.category_metalloids, 4}, 59 | {22, R.string.category_noble_gases, 1}, 60 | {23, R.string.category_polyatomic_nonmetals, 5}, 61 | {24, R.string.category_other_metals, 6}, 62 | {25, R.string.category_transition_metals, 7}, 63 | {26, R.string.category_unknown, 10}, 64 | {92, "57 - 71", 8}, 65 | {110, "89 - 103", 9} 66 | }; 67 | 68 | private class ViewHolder { 69 | TextView symbol, number, name, weight; 70 | } 71 | 72 | public static final Creator CREATOR = new Creator() { 73 | @Override 74 | public TableAdapter createFromParcel(Parcel in) { 75 | return new TableAdapter(in); 76 | } 77 | 78 | @Override 79 | public TableAdapter[] newArray(int size) { 80 | return new TableAdapter[size]; 81 | } 82 | }; 83 | 84 | public TableAdapter() { 85 | } 86 | 87 | protected TableAdapter(Parcel in) { 88 | mGroupsCount = in.readInt(); 89 | mPeriodsCount = in.readInt(); 90 | 91 | final int size = in.readInt(); 92 | for (int i = 0; i < size; i++) { 93 | final int key = in.readInt(); 94 | final TableItem value = in.readParcelable(TableItem.class.getClassLoader()); 95 | 96 | mItems.put(key, value); 97 | } 98 | } 99 | 100 | @Override 101 | public int getCount() { 102 | return mItems.size(); 103 | } 104 | 105 | @Override 106 | public TableItem getItem(int position) { 107 | return mItems.get(position); 108 | } 109 | 110 | @Override 111 | public long getItemId(int position) { 112 | return position; 113 | } 114 | 115 | @Override 116 | public View getView(int position, View convertView, ViewGroup parent) { 117 | final Context context = parent.getContext(); 118 | 119 | initialize(context); 120 | 121 | final TableItem item = getItem(position); 122 | 123 | if (item instanceof TableTextItem) { 124 | if (convertView == null) { 125 | convertView = LayoutInflater.from(context).inflate(R.layout.table_text, 126 | parent, false); 127 | } 128 | 129 | convertView.setBackgroundColor(getBackgroundColor(context, item)); 130 | 131 | ((TextView) convertView).setText(((TableTextItem) item).getText()); 132 | 133 | return convertView; 134 | } else if (item instanceof TableElementItem) { 135 | return getView((TableElementItem) item, convertView, parent); 136 | } 137 | 138 | return null; 139 | } 140 | 141 | public View getView(TableElementItem item, View convertView, ViewGroup parent) { 142 | final Context context = parent.getContext(); 143 | 144 | initialize(context); 145 | 146 | if (convertView == null) { 147 | convertView = LayoutInflater.from(context).inflate(R.layout.table_item, 148 | parent, false); 149 | } 150 | 151 | ViewHolder viewHolder = (ViewHolder) convertView.getTag(); 152 | 153 | if (viewHolder == null) { 154 | viewHolder = new ViewHolder(); 155 | 156 | viewHolder.symbol = (TextView) convertView.findViewById(R.id.element_symbol); 157 | viewHolder.symbol.setTypeface(mTypeface); 158 | viewHolder.number = (TextView) convertView.findViewById(R.id.element_number); 159 | viewHolder.number.setTypeface(mTypeface); 160 | viewHolder.name = (TextView) convertView.findViewById(R.id.element_name); 161 | viewHolder.name.setTypeface(mTypeface); 162 | viewHolder.weight = (TextView) convertView.findViewById(R.id.element_weight); 163 | viewHolder.weight.setTypeface(mTypeface); 164 | 165 | convertView.setTag(viewHolder); 166 | } 167 | 168 | String atomicWeight = item.getStandardAtomicWeight(); 169 | 170 | try { 171 | BigDecimal bigDecimal = new BigDecimal(atomicWeight); 172 | atomicWeight = bigDecimal.setScale(3, RoundingMode.HALF_UP).toString(); 173 | } catch (NumberFormatException ignored) { 174 | } 175 | 176 | convertView.setBackgroundColor(getBackgroundColor(context, item)); 177 | 178 | viewHolder.symbol.setText(item.getSymbol()); 179 | viewHolder.number.setText(String.valueOf(item.getNumber())); 180 | viewHolder.name.setTextSize(12f); 181 | viewHolder.name.setText(item.getName()); 182 | viewHolder.weight.setText(atomicWeight); 183 | 184 | return convertView; 185 | } 186 | 187 | @Override 188 | public View getActiveView(Bitmap bitmap, View convertView, ViewGroup parent) { 189 | initialize(parent.getContext()); 190 | 191 | if (convertView == null) { 192 | convertView = LayoutInflater.from(parent.getContext()).inflate( 193 | R.layout.table_active_item, parent, false); 194 | } 195 | 196 | ImageView imageView = (ImageView) convertView.findViewById(R.id.bitmap); 197 | imageView.setImageBitmap(bitmap); 198 | 199 | return convertView; 200 | } 201 | 202 | private int getBackgroundColor(Context context, TableItem item) { 203 | return context.getResources().getColor(COLORS[item.getCategory()]); 204 | } 205 | 206 | @Override 207 | public int getItemViewType(int position) { 208 | if (getItem(position) instanceof TableTextItem) { 209 | return ViewType.TEXT.ordinal(); 210 | } 211 | 212 | return ViewType.ITEM.ordinal(); 213 | } 214 | 215 | @Override 216 | public int getViewTypeCount() { 217 | return ViewType.values().length; 218 | } 219 | 220 | public void setItems(Context context, List items) { 221 | mItems.clear(); 222 | 223 | if (items == null) { 224 | return; 225 | } 226 | 227 | int groups = 0, periods = 0; 228 | 229 | for (TableElementItem item : items) { 230 | groups = Math.max(item.getGroup(), groups); 231 | periods = Math.max(item.getPeriod(), periods); 232 | } 233 | 234 | mGroupsCount = groups; 235 | mPeriodsCount = periods + 2; 236 | 237 | for (TableElementItem item : items) { 238 | final int position; 239 | 240 | if (item.getNumber() >= 57 && item.getNumber() <= 71) { 241 | position = (mGroupsCount * periods) + 2 + item.getNumber() - 57; 242 | } else if (item.getNumber() >= 89 && item.getNumber() <= 103) { 243 | position = (mGroupsCount * periods) + 20 + item.getNumber() - 89; 244 | } else { 245 | position = ((item.getPeriod() - 1) * mGroupsCount) + item.getGroup() - 1; 246 | } 247 | 248 | mItems.put(position, item); 249 | } 250 | 251 | for (Object[] item : TEXT_ITEMS) { 252 | final String text; 253 | 254 | if (item[1] instanceof Integer) { 255 | text = context.getString((int) item[1]); 256 | } else { 257 | text = (String) item[1]; 258 | } 259 | 260 | mItems.put((int) item[0], new TableTextItem(text, (int) item[2])); 261 | } 262 | } 263 | 264 | @Override 265 | public int getGroupsCount() { 266 | return mGroupsCount; 267 | } 268 | 269 | @Override 270 | public int getPeriodsCount() { 271 | return mPeriodsCount; 272 | } 273 | 274 | @Override 275 | public boolean areAllItemsEnabled() { 276 | return false; 277 | } 278 | 279 | @Override 280 | public boolean isEnabled(int position) { 281 | return getItem(position) instanceof TableElementItem; 282 | } 283 | 284 | @Override 285 | public int describeContents() { 286 | return 0; 287 | } 288 | 289 | @Override 290 | public void writeToParcel(Parcel dest, int flags) { 291 | dest.writeInt(mGroupsCount); 292 | dest.writeInt(mPeriodsCount); 293 | 294 | dest.writeInt(mItems.size()); 295 | for (Map.Entry item : mItems.entrySet()) { 296 | dest.writeInt(item.getKey()); 297 | dest.writeParcelable((Parcelable) item.getValue(), 0); 298 | } 299 | } 300 | 301 | private void initialize(Context context) { 302 | if (mTypeface == null) { 303 | mTypeface = Typeface.createFromAsset(context.getAssets(), "fonts/NotoSans-Regular.ttf"); 304 | } 305 | } 306 | } 307 | -------------------------------------------------------------------------------- /PeriodicTable/src/main/java/com/frozendevs/periodictable/model/adapter/IsotopesAdapter.java: -------------------------------------------------------------------------------- 1 | package com.frozendevs.periodictable.model.adapter; 2 | 3 | import android.content.Context; 4 | import android.graphics.Typeface; 5 | import android.view.ContextMenu; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.TextView; 10 | 11 | import com.frozendevs.periodictable.R; 12 | import com.frozendevs.periodictable.model.Isotope; 13 | import com.frozendevs.periodictable.view.ExpandableIndicatorView; 14 | import com.h6ah4i.android.widget.advrecyclerview.expandable.RecyclerViewExpandableItemManager; 15 | import com.h6ah4i.android.widget.advrecyclerview.utils.AbstractExpandableItemAdapter; 16 | import com.h6ah4i.android.widget.advrecyclerview.utils.AbstractExpandableItemViewHolder; 17 | 18 | public class IsotopesAdapter extends AbstractExpandableItemAdapter { 20 | 21 | private Typeface mTypeface; 22 | private IsotopeProperties[] mProperties = new IsotopeProperties[0]; 23 | 24 | private class Property { 25 | String mName = "", mValue = "", mValueRaw = ""; 26 | 27 | Property(Context context, String value) { 28 | if (value != null) { 29 | mValueRaw = value; 30 | if (!value.equals("")) mValue = value; 31 | else mValue = context.getString(R.string.property_value_unknown); 32 | } 33 | } 34 | 35 | Property(Context context, int name, String value) { 36 | this(context, value); 37 | 38 | mName = context.getString(name); 39 | } 40 | 41 | Property(Context context, int name, String value, int noneValue) { 42 | this(context, name, value); 43 | 44 | if (value != null && value.equals("")) { 45 | mValue = context.getString(noneValue); 46 | } 47 | } 48 | 49 | Property(Context context, int name, String value, int noneValue, int specialValue) { 50 | this(context, name, value, noneValue); 51 | 52 | if (value != null && value.equals("-")) { 53 | mValue = context.getString(specialValue); 54 | } 55 | } 56 | 57 | String getName() { 58 | return mName; 59 | } 60 | 61 | String getValue() { 62 | return mValue; 63 | } 64 | 65 | String getValueRaw() { 66 | return mValueRaw; 67 | } 68 | } 69 | 70 | private class IsotopeProperties { 71 | 72 | Property symbol, halfLife, spin, abundance, decayModes; 73 | 74 | public IsotopeProperties(Context context, Isotope isotope) { 75 | symbol = new Property(context, isotope.getSymbol()); 76 | halfLife = new Property(context, R.string.property_half_life, isotope.getHalfLife(), 77 | R.string.property_value_unknown, R.string.property_value_stable); 78 | spin = new Property(context, R.string.property_spin, isotope.getSpin(), 79 | R.string.property_value_unknown); 80 | abundance = new Property(context, R.string.property_abundance, isotope.getAbundance(), 81 | R.string.property_value_none, R.string.property_value_trace); 82 | decayModes = new Property(context, 83 | R.string.property_decay_modes, isotope.getHalfLife().equals("-") ? 84 | context.getString(R.string.property_value_none) : isotope.getDecayModes()); 85 | } 86 | 87 | public Property getSymbol() { 88 | return symbol; 89 | } 90 | 91 | public Property getHalfLife() { 92 | return halfLife; 93 | } 94 | 95 | public Property getSpin() { 96 | return spin; 97 | } 98 | 99 | public Property getAbundance() { 100 | return abundance; 101 | } 102 | 103 | public Property getDecayModes() { 104 | return decayModes; 105 | } 106 | } 107 | 108 | public class GroupViewHolder extends AbstractExpandableItemViewHolder implements 109 | View.OnClickListener, View.OnCreateContextMenuListener { 110 | private TextView mSymbol, mHalfLife, mAbundance; 111 | private ExpandableIndicatorView mIndicator; 112 | 113 | public GroupViewHolder(View view) { 114 | super(view); 115 | 116 | mIndicator = (ExpandableIndicatorView) view.findViewById(R.id.group_indicator); 117 | mSymbol = (TextView) view.findViewById(R.id.property_symbol); 118 | mHalfLife = (TextView) view.findViewById(R.id.property_half_life); 119 | mAbundance = (TextView) view.findViewById(R.id.property_abundance); 120 | 121 | view.setOnClickListener(this); 122 | view.setOnCreateContextMenuListener(this); 123 | } 124 | 125 | public void setIndicatorState(boolean expanded) { 126 | mIndicator.setStateExpanded(expanded); 127 | } 128 | 129 | public void setSymbol(String symbol) { 130 | mSymbol.setText(symbol); 131 | } 132 | 133 | public void setHalfLife(String halfLife) { 134 | mHalfLife.setText(halfLife); 135 | } 136 | 137 | public void setAbundance(String abundance) { 138 | mAbundance.setText(abundance); 139 | } 140 | 141 | public void setTypeface(Typeface typeface) { 142 | mSymbol.setTypeface(typeface); 143 | mHalfLife.setTypeface(typeface); 144 | mAbundance.setTypeface(typeface); 145 | } 146 | 147 | @Override 148 | public void onClick(View view) { 149 | } 150 | 151 | @Override 152 | public void onCreateContextMenu(ContextMenu menu, View view, 153 | ContextMenu.ContextMenuInfo menuInfo) { 154 | } 155 | } 156 | 157 | public class ChildViewHolder extends AbstractExpandableItemViewHolder implements 158 | View.OnClickListener, View.OnCreateContextMenuListener { 159 | private TextView mName, mValue; 160 | 161 | public ChildViewHolder(View view) { 162 | super(view); 163 | 164 | mName = (TextView) view.findViewById(R.id.property_name); 165 | mValue = (TextView) view.findViewById(R.id.property_value); 166 | 167 | view.setOnClickListener(this); 168 | view.setOnCreateContextMenuListener(this); 169 | } 170 | 171 | public void setName(String name) { 172 | mName.setText(name); 173 | } 174 | 175 | public void setValue(String value) { 176 | mValue.setText(value); 177 | } 178 | 179 | public void setTypeface(Typeface typeface) { 180 | mName.setTypeface(typeface); 181 | mValue.setTypeface(typeface); 182 | } 183 | 184 | @Override 185 | public void onClick(View view) { 186 | } 187 | 188 | @Override 189 | public void onCreateContextMenu(ContextMenu menu, View view, 190 | ContextMenu.ContextMenuInfo menuInfo) { 191 | } 192 | } 193 | 194 | public IsotopesAdapter(Context context, Isotope[] isotopes) { 195 | mTypeface = Typeface.createFromAsset(context.getAssets(), "fonts/NotoSans-Regular.ttf"); 196 | 197 | if (isotopes != null) { 198 | mProperties = new IsotopeProperties[isotopes.length]; 199 | 200 | for (int i = 0; i < isotopes.length; i++) { 201 | mProperties[i] = new IsotopeProperties(context, isotopes[i]); 202 | } 203 | } 204 | 205 | setHasStableIds(true); 206 | } 207 | 208 | @Override 209 | public int getGroupCount() { 210 | return mProperties.length; 211 | } 212 | 213 | @Override 214 | public int getChildCount(int groupPosition) { 215 | return 4; 216 | } 217 | 218 | @Override 219 | public long getGroupId(int groupPosition) { 220 | return mProperties[groupPosition].hashCode(); 221 | } 222 | 223 | @Override 224 | public long getChildId(int groupPosition, int childPosition) { 225 | return getChild(groupPosition, childPosition).hashCode(); 226 | } 227 | 228 | @Override 229 | public int getGroupItemViewType(int groupPosition) { 230 | return 0; 231 | } 232 | 233 | @Override 234 | public int getChildItemViewType(int groupPosition, int childPosition) { 235 | return 0; 236 | } 237 | 238 | @Override 239 | public GroupViewHolder onCreateGroupViewHolder(ViewGroup viewGroup, int viewType) { 240 | GroupViewHolder viewHolder = new GroupViewHolder(LayoutInflater.from(viewGroup. 241 | getContext()).inflate(R.layout.isotope_list_item, viewGroup, false)); 242 | 243 | viewHolder.setTypeface(mTypeface); 244 | 245 | return viewHolder; 246 | } 247 | 248 | @Override 249 | public ChildViewHolder onCreateChildViewHolder(ViewGroup viewGroup, int viewType) { 250 | ChildViewHolder viewHolder = new ChildViewHolder(LayoutInflater.from(viewGroup. 251 | getContext()).inflate(R.layout.properties_list_item, viewGroup, false)); 252 | 253 | viewHolder.setTypeface(mTypeface); 254 | 255 | return viewHolder; 256 | } 257 | 258 | @Override 259 | public void onBindGroupViewHolder(GroupViewHolder groupViewHolder, int groupPosition, 260 | int viewType) { 261 | IsotopeProperties properties = mProperties[groupPosition]; 262 | 263 | groupViewHolder.setSymbol(properties.getSymbol().getValue()); 264 | groupViewHolder.setHalfLife(""); 265 | groupViewHolder.setAbundance(""); 266 | if (!properties.getHalfLife().getValueRaw().equals("")) { 267 | groupViewHolder.setHalfLife(properties.getHalfLife().getValue()); 268 | 269 | if (!properties.getAbundance().getValueRaw().equals("")) { 270 | groupViewHolder.setAbundance(properties.getAbundance().getValue()); 271 | } 272 | } 273 | 274 | final int expandState = groupViewHolder.getExpandStateFlags(); 275 | 276 | if ((expandState & RecyclerViewExpandableItemManager.STATE_FLAG_IS_UPDATED) != 0) { 277 | groupViewHolder.setIndicatorState((expandState & RecyclerViewExpandableItemManager. 278 | STATE_FLAG_IS_EXPANDED) != 0); 279 | } 280 | } 281 | 282 | @Override 283 | public void onBindChildViewHolder(ChildViewHolder childViewHolder, int groupPosition, 284 | int childPosition, int viewType) { 285 | Property property = getChild(groupPosition, childPosition); 286 | 287 | childViewHolder.setName(property.getName()); 288 | childViewHolder.setValue(property.getValue()); 289 | } 290 | 291 | @Override 292 | public boolean onCheckCanExpandOrCollapseGroup(GroupViewHolder groupViewHolder, 293 | int groupPosition, int x, int y, 294 | boolean expand) { 295 | return true; 296 | } 297 | 298 | private Property getChild(int groupPosition, int childPosition) { 299 | IsotopeProperties properties = mProperties[groupPosition]; 300 | 301 | switch (childPosition) { 302 | case 0: 303 | return properties.getHalfLife(); 304 | case 1: 305 | return properties.getDecayModes(); 306 | case 2: 307 | return properties.getSpin(); 308 | default: 309 | return properties.getAbundance(); 310 | } 311 | } 312 | } 313 | -------------------------------------------------------------------------------- /PeriodicTable/src/main/java/com/frozendevs/periodictable/view/PeriodicTableView.java: -------------------------------------------------------------------------------- 1 | package com.frozendevs.periodictable.view; 2 | 3 | import android.content.Context; 4 | import android.database.DataSetObserver; 5 | import android.graphics.Bitmap; 6 | import android.graphics.Canvas; 7 | import android.graphics.Matrix; 8 | import android.graphics.Paint; 9 | import android.os.AsyncTask; 10 | import android.os.Parcel; 11 | import android.os.Parcelable; 12 | import android.support.v4.util.LruCache; 13 | import android.util.AttributeSet; 14 | import android.view.MotionEvent; 15 | import android.view.SoundEffectConstants; 16 | import android.view.View; 17 | import android.view.ViewGroup; 18 | import android.view.accessibility.AccessibilityEvent; 19 | import android.widget.BaseAdapter; 20 | 21 | import com.frozendevs.periodictable.R; 22 | 23 | import java.lang.ref.SoftReference; 24 | import java.util.HashMap; 25 | import java.util.Map; 26 | 27 | public class PeriodicTableView extends ZoomableScrollView { 28 | 29 | private final float DEFAULT_SPACING = 1f; 30 | 31 | private View mEmptyView = null; 32 | private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); 33 | private Adapter mAdapter; 34 | private Matrix mMatrix = new Matrix(); 35 | private OnItemClickListener mOnItemClickListener; 36 | private View mActiveView; 37 | private LruCache mBitmapCache; 38 | private int mTileSize; 39 | private GenerateBitmapsTask mGenerateBitmapsTask; 40 | 41 | public interface OnItemClickListener { 42 | void onItemClick(PeriodicTableView parent, View view, int position); 43 | } 44 | 45 | public static abstract class Adapter extends BaseAdapter { 46 | public abstract View getActiveView(Bitmap bitmap, View convertView, ViewGroup parent); 47 | 48 | public abstract int getGroupsCount(); 49 | 50 | public abstract int getPeriodsCount(); 51 | } 52 | 53 | private abstract class OnClickConfirmedListener { 54 | abstract void onClickConfirmed(int position); 55 | } 56 | 57 | private DataSetObserver mDataSetObserver = new DataSetObserver() { 58 | @Override 59 | public void onChanged() { 60 | updateEmptyStatus(true); 61 | 62 | if (mAdapter.isEmpty()) { 63 | return; 64 | } 65 | 66 | if (mBitmapCache.size() < mAdapter.getCount()) { 67 | if (mGenerateBitmapsTask != null) { 68 | mGenerateBitmapsTask.cancel(true); 69 | } 70 | 71 | mGenerateBitmapsTask = new GenerateBitmapsTask(); 72 | mGenerateBitmapsTask.execute(); 73 | } else { 74 | onGenerateComplete(); 75 | } 76 | } 77 | }; 78 | 79 | private class GenerateBitmapsTask extends AsyncTask { 80 | @Override 81 | protected Void doInBackground(Void... params) { 82 | final int size = mAdapter.getGroupsCount() * mAdapter.getPeriodsCount(); 83 | 84 | final Map> convertViews = new HashMap<>(); 85 | 86 | for (int position = 0; position < size; position++) { 87 | if (isCancelled()) { 88 | return null; 89 | } 90 | 91 | if (mBitmapCache.get(position) != null) { 92 | continue; 93 | } 94 | 95 | final int viewType = mAdapter.getItemViewType(position); 96 | 97 | View convertView = null; 98 | 99 | final SoftReference softReference = convertViews.get(viewType); 100 | 101 | if (softReference != null) { 102 | convertView = softReference.get(); 103 | } 104 | 105 | convertView = mAdapter.getView(position, convertView, PeriodicTableView.this); 106 | 107 | if (convertView != null) { 108 | final Bitmap bitmap = generateBitmap(convertView); 109 | 110 | if (bitmap != null) { 111 | mBitmapCache.put(position, bitmap); 112 | } 113 | } 114 | 115 | if (softReference == null || softReference.get() == null) { 116 | convertViews.put(viewType, new SoftReference<>(convertView)); 117 | } 118 | } 119 | 120 | return null; 121 | } 122 | 123 | @Override 124 | protected void onPostExecute(Void result) { 125 | if (!isCancelled()) { 126 | onGenerateComplete(); 127 | } 128 | } 129 | 130 | private Bitmap generateBitmap(View view) { 131 | Bitmap bitmap = null; 132 | 133 | ViewGroup.LayoutParams layoutParams = view.getLayoutParams(); 134 | 135 | mTileSize = Math.max(mTileSize, Math.max(layoutParams.width, 136 | layoutParams.height)); 137 | 138 | view.measure(View.MeasureSpec.makeMeasureSpec(layoutParams.width, 139 | View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec( 140 | layoutParams.height, View.MeasureSpec.EXACTLY)); 141 | view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight()); 142 | 143 | view.buildDrawingCache(); 144 | 145 | final Bitmap drawingCache = view.getDrawingCache(); 146 | 147 | if (drawingCache != null) { 148 | bitmap = Bitmap.createBitmap(drawingCache); 149 | } 150 | 151 | view.destroyDrawingCache(); 152 | 153 | return bitmap; 154 | } 155 | } 156 | 157 | private static class SavedState extends BaseSavedState { 158 | 159 | int activeViewPosition = -1; 160 | int tileSize; 161 | 162 | public SavedState(Parcel source) { 163 | super(source); 164 | 165 | activeViewPosition = source.readInt(); 166 | tileSize = source.readInt(); 167 | } 168 | 169 | public SavedState(Parcelable superState) { 170 | super(superState); 171 | } 172 | 173 | @Override 174 | public void writeToParcel(Parcel out, int flags) { 175 | super.writeToParcel(out, flags); 176 | 177 | out.writeInt(activeViewPosition); 178 | out.writeInt(tileSize); 179 | } 180 | 181 | public static final Parcelable.Creator CREATOR = 182 | new Parcelable.Creator() { 183 | public SavedState createFromParcel(Parcel in) { 184 | return new SavedState(in); 185 | } 186 | 187 | public SavedState[] newArray(int size) { 188 | return new SavedState[size]; 189 | } 190 | }; 191 | } 192 | 193 | private OnClickConfirmedListener mOnSingleTapConfirmed = new OnClickConfirmedListener() { 194 | @Override 195 | void onClickConfirmed(int position) { 196 | playSoundEffect(SoundEffectConstants.CLICK); 197 | 198 | mOnItemClickListener.onItemClick(PeriodicTableView.this, mActiveView, position); 199 | 200 | if (mActiveView != null) { 201 | mActiveView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); 202 | } 203 | } 204 | }; 205 | 206 | private OnClickConfirmedListener mOnDownConfirmed; 207 | 208 | public PeriodicTableView(Context context) { 209 | super(context); 210 | 211 | initPeriodicTableView(); 212 | } 213 | 214 | public PeriodicTableView(Context context, AttributeSet attrs) { 215 | super(context, attrs); 216 | 217 | initPeriodicTableView(); 218 | } 219 | 220 | public PeriodicTableView(Context context, AttributeSet attrs, int defStyleAttr) { 221 | super(context, attrs, defStyleAttr); 222 | 223 | initPeriodicTableView(); 224 | } 225 | 226 | private void initPeriodicTableView() { 227 | mOnDownConfirmed = new OnClickConfirmedListener() { 228 | @Override 229 | void onClickConfirmed(int position) { 230 | addActiveView(position); 231 | } 232 | }; 233 | } 234 | 235 | public void setEmptyView(View view) { 236 | mEmptyView = view; 237 | 238 | updateEmptyStatus(mAdapter == null || mAdapter.isEmpty() || mBitmapCache == null || 239 | mBitmapCache.size() < mAdapter.getCount()); 240 | } 241 | 242 | private void updateEmptyStatus(boolean empty) { 243 | if (mEmptyView != null) { 244 | mEmptyView.setVisibility(empty ? VISIBLE : GONE); 245 | } 246 | } 247 | 248 | private float getScaledTileSize() { 249 | return getZoom() * mTileSize; 250 | } 251 | 252 | @Override 253 | public boolean onSingleTapConfirmed(MotionEvent event) { 254 | return mOnItemClickListener != null && processClick(event, mOnSingleTapConfirmed); 255 | } 256 | 257 | @Override 258 | protected int getScaledWidth() { 259 | int groups = mAdapter != null ? mAdapter.getGroupsCount() : 0; 260 | 261 | return Math.round((getScaledTileSize() * groups) + ((groups - 1) * DEFAULT_SPACING)); 262 | } 263 | 264 | @Override 265 | protected int getScaledHeight() { 266 | int periods = mAdapter != null ? mAdapter.getPeriodsCount() : 0; 267 | 268 | return Math.round((getScaledTileSize() * periods) + 269 | ((periods - 1) * DEFAULT_SPACING)); 270 | } 271 | 272 | public void setAdapter(Adapter adapter) { 273 | if (adapter != null && mBitmapCache == null) { 274 | throw new IllegalStateException("Initialize bitmap cache using setBitmapCache() first"); 275 | } 276 | 277 | if (mAdapter != null) { 278 | mAdapter.unregisterDataSetObserver(mDataSetObserver); 279 | } 280 | 281 | mAdapter = adapter; 282 | 283 | if (mAdapter != null) { 284 | mAdapter.registerDataSetObserver(mDataSetObserver); 285 | } 286 | } 287 | 288 | @Override 289 | public float getMinimalZoom() { 290 | final int groups = mAdapter.getGroupsCount(); 291 | final int periods = mAdapter.getPeriodsCount(); 292 | final int tileSize = mTileSize; 293 | 294 | return Math.min((getWidth() - ((groups - 1) * DEFAULT_SPACING)) / groups, 295 | (getHeight() - ((periods - 1) * DEFAULT_SPACING)) / periods) / tileSize; 296 | } 297 | 298 | @Override 299 | protected void onDetachedFromWindow() { 300 | super.onDetachedFromWindow(); 301 | 302 | if (mAdapter != null) { 303 | mAdapter.unregisterDataSetObserver(mDataSetObserver); 304 | } 305 | } 306 | 307 | public void setOnItemClickListener(OnItemClickListener listener) { 308 | mOnItemClickListener = listener; 309 | } 310 | 311 | @Override 312 | public void onDraw(Canvas canvas) { 313 | adjustActiveView(); 314 | 315 | super.onDraw(canvas); 316 | } 317 | 318 | @Override 319 | protected void dispatchDraw(Canvas canvas) { 320 | if (mBitmapCache != null && mAdapter != null && !mAdapter.isEmpty()) { 321 | float tileSize = getScaledTileSize(); 322 | 323 | float y = (getHeight() - getScaledHeight()) / 2f; 324 | 325 | for (int row = 0; row < mAdapter.getPeriodsCount(); row++) { 326 | float x = (getWidth() - getScaledWidth()) / 2f; 327 | 328 | for (int column = 0; column < mAdapter.getGroupsCount(); column++) { 329 | if (x + tileSize > getScrollX() && x < getScrollX() + getWidth() && 330 | y + tileSize > getScrollY() && y < getScrollY() + getHeight()) { 331 | int position = (row * mAdapter.getGroupsCount()) + column; 332 | 333 | if (mActiveView != null && indexOfChild(mActiveView) >= 0 && 334 | position == (int) mActiveView.getTag(R.id.active_view_position)) { 335 | adjustActiveView(); 336 | } else { 337 | Bitmap bitmap = mBitmapCache.get(position); 338 | 339 | if (bitmap != null && !bitmap.isRecycled()) { 340 | mMatrix.reset(); 341 | mMatrix.postScale(getZoom(), getZoom()); 342 | mMatrix.postTranslate(x, y); 343 | 344 | canvas.drawBitmap(bitmap, mMatrix, mPaint); 345 | } 346 | } 347 | } 348 | 349 | x += tileSize + DEFAULT_SPACING; 350 | } 351 | 352 | y += tileSize + DEFAULT_SPACING; 353 | } 354 | } 355 | 356 | super.dispatchDraw(canvas); 357 | } 358 | 359 | private void adjustActiveView() { 360 | if (mActiveView != null) { 361 | int position = (int) mActiveView.getTag(R.id.active_view_position); 362 | 363 | float tileSize = getScaledTileSize(); 364 | 365 | mActiveView.setScaleX(getZoom()); 366 | mActiveView.setScaleY(getZoom()); 367 | mActiveView.setTranslationX(((getWidth() - getScaledWidth()) / 2f) + 368 | ((position % mAdapter.getGroupsCount()) * (tileSize + DEFAULT_SPACING))); 369 | mActiveView.setTranslationY(((getHeight() - getScaledHeight()) / 2f) + 370 | ((position / mAdapter.getGroupsCount()) * (tileSize + DEFAULT_SPACING))); 371 | } 372 | } 373 | 374 | private void addActiveView(int position) { 375 | if (mActiveView != null) { 376 | if (position == (int) mActiveView.getTag(R.id.active_view_position)) { 377 | adjustActiveView(); 378 | 379 | return; 380 | } 381 | 382 | removeView(mActiveView); 383 | } 384 | 385 | mActiveView = mAdapter.getActiveView(mBitmapCache.get(position), mActiveView, this); 386 | 387 | if (mActiveView != null) { 388 | mActiveView.setTag(R.id.active_view_position, position); 389 | mActiveView.setPivotX(0f); 390 | mActiveView.setPivotY(0f); 391 | 392 | adjustActiveView(); 393 | 394 | addView(mActiveView); 395 | } 396 | } 397 | 398 | @Override 399 | public Parcelable onSaveInstanceState() { 400 | SavedState savedState = new SavedState(super.onSaveInstanceState()); 401 | 402 | savedState.tileSize = mTileSize; 403 | 404 | if (mActiveView != null) { 405 | savedState.activeViewPosition = (int) mActiveView.getTag(R.id.active_view_position); 406 | } 407 | 408 | return savedState; 409 | } 410 | 411 | @Override 412 | public void onRestoreInstanceState(Parcelable state) { 413 | if (state instanceof SavedState) { 414 | SavedState savedState = (SavedState) state; 415 | 416 | mTileSize = savedState.tileSize; 417 | 418 | if (!mAdapter.isEmpty() && savedState.activeViewPosition > -1) { 419 | addActiveView(savedState.activeViewPosition); 420 | } 421 | 422 | super.onRestoreInstanceState(savedState.getSuperState()); 423 | } else { 424 | super.onRestoreInstanceState(state); 425 | } 426 | } 427 | 428 | @Override 429 | protected void onLayout(boolean changed, int l, int t, int r, int b) { 430 | super.onLayout(changed, l, t, r, b); 431 | 432 | adjustActiveView(); 433 | } 434 | 435 | public View getActiveView() { 436 | return mActiveView; 437 | } 438 | 439 | @Override 440 | public boolean onDown(MotionEvent event) { 441 | super.onDown(event); 442 | 443 | return processClick(event, mOnDownConfirmed); 444 | } 445 | 446 | private boolean processClick(MotionEvent event, OnClickConfirmedListener listener) { 447 | if (listener != null && mAdapter != null && !mAdapter.isEmpty()) { 448 | final float rawX = event.getX() + getScrollX(); 449 | final float rawY = event.getY() + getScrollY(); 450 | final float tileSize = getScaledTileSize(); 451 | final int scaledWidth = getScaledWidth(); 452 | final int scaledHeight = getScaledHeight(); 453 | final float startY = (getHeight() - scaledHeight) / 2f; 454 | final float startX = (getWidth() - scaledWidth) / 2f; 455 | 456 | if (rawX >= startX && rawX <= startX + scaledWidth && 457 | rawY >= startY && rawY <= startY + scaledHeight) { 458 | final int position = ((int) ((rawY - startY) / (tileSize + DEFAULT_SPACING)) * 459 | mAdapter.getGroupsCount()) + (int) ((rawX - startX) / (tileSize + DEFAULT_SPACING)); 460 | 461 | final int size = mAdapter.getGroupsCount() * mAdapter.getPeriodsCount(); 462 | 463 | if (position >= 0 && position < size && mAdapter.isEnabled(position)) { 464 | listener.onClickConfirmed(position); 465 | 466 | return true; 467 | } 468 | } 469 | } 470 | 471 | return false; 472 | } 473 | 474 | public void setBitmapCache(LruCache bitmapCache) { 475 | mBitmapCache = bitmapCache; 476 | } 477 | 478 | private void onGenerateComplete() { 479 | if (mActiveView == null) { 480 | final int size = mAdapter.getGroupsCount() * mAdapter.getPeriodsCount(); 481 | 482 | for (int i = 0; i < size; i++) { 483 | if (mAdapter.getItem(i) != null && mAdapter.isEnabled(i)) { 484 | addActiveView(i); 485 | 486 | break; 487 | } 488 | } 489 | } 490 | 491 | invalidate(); 492 | 493 | updateEmptyStatus(false); 494 | } 495 | } 496 | -------------------------------------------------------------------------------- /PeriodicTable/src/main/java/com/frozendevs/periodictable/view/ZoomableScrollView.java: -------------------------------------------------------------------------------- 1 | package com.frozendevs.periodictable.view; 2 | 3 | import android.annotation.TargetApi; 4 | import android.content.Context; 5 | import android.graphics.Canvas; 6 | import android.graphics.Point; 7 | import android.os.Build; 8 | import android.support.v4.view.ViewCompat; 9 | import android.support.v4.widget.EdgeEffectCompat; 10 | import android.util.AttributeSet; 11 | import android.view.GestureDetector; 12 | import android.view.MotionEvent; 13 | import android.view.ScaleGestureDetector; 14 | import android.widget.FrameLayout; 15 | import android.widget.OverScroller; 16 | 17 | import com.frozendevs.periodictable.R; 18 | import com.frozendevs.periodictable.widget.Zoomer; 19 | 20 | public class ZoomableScrollView extends FrameLayout implements GestureDetector.OnGestureListener, 21 | ScaleGestureDetector.OnScaleGestureListener, GestureDetector.OnDoubleTapListener { 22 | 23 | private static final float DEFAULT_MAX_ZOOM = 1f; 24 | 25 | private OverScroller mOverScroller; 26 | private Zoomer mZoomer; 27 | private ScaleGestureDetector mScaleDetector; 28 | private GestureDetector mGestureDetector; 29 | private EdgeEffectCompat mEdgeEffectTop; 30 | private EdgeEffectCompat mEdgeEffectBottom; 31 | private EdgeEffectCompat mEdgeEffectLeft; 32 | private EdgeEffectCompat mEdgeEffectRight; 33 | private boolean mIsScrolling = false; 34 | private float mMinZoom = 0f; 35 | private float mZoom = 0f; 36 | private float mMaxZoom = 1f; 37 | private Point mZoomFocalPoint = new Point(); 38 | private float mStartZoom; 39 | private boolean mEdgeEffectTopActive; 40 | private boolean mEdgeEffectBottomActive; 41 | private boolean mEdgeEffectLeftActive; 42 | private boolean mEdgeEffectRightActive; 43 | 44 | public ZoomableScrollView(Context context) { 45 | super(context); 46 | 47 | initZoomableScrollView(context); 48 | } 49 | 50 | public ZoomableScrollView(Context context, AttributeSet attrs) { 51 | super(context, attrs, R.attr.zoomableScrollViewStyle); 52 | 53 | initZoomableScrollView(context); 54 | } 55 | 56 | public ZoomableScrollView(Context context, AttributeSet attrs, int defStyle) { 57 | super(context, attrs, defStyle); 58 | 59 | initZoomableScrollView(context); 60 | } 61 | 62 | private void initZoomableScrollView(Context context) { 63 | setWillNotDraw(false); 64 | 65 | setHorizontalScrollBarEnabled(true); 66 | setVerticalScrollBarEnabled(true); 67 | 68 | setClickable(true); 69 | 70 | mOverScroller = new OverScroller(context); 71 | mZoomer = new Zoomer(context); 72 | mScaleDetector = new ScaleGestureDetector(context, this); 73 | mGestureDetector = new GestureDetector(context, this); 74 | 75 | mEdgeEffectTop = new EdgeEffectCompat(context); 76 | mEdgeEffectBottom = new EdgeEffectCompat(context); 77 | mEdgeEffectLeft = new EdgeEffectCompat(context); 78 | mEdgeEffectRight = new EdgeEffectCompat(context); 79 | } 80 | 81 | @Override 82 | public boolean onDown(MotionEvent e) { 83 | mEdgeEffectLeftActive 84 | = mEdgeEffectTopActive 85 | = mEdgeEffectRightActive 86 | = mEdgeEffectBottomActive 87 | = false; 88 | 89 | mEdgeEffectLeft.onRelease(); 90 | mEdgeEffectTop.onRelease(); 91 | mEdgeEffectRight.onRelease(); 92 | mEdgeEffectBottom.onRelease(); 93 | 94 | mOverScroller.forceFinished(true); 95 | 96 | return false; 97 | } 98 | 99 | @Override 100 | public void onShowPress(MotionEvent e) { 101 | } 102 | 103 | @Override 104 | public boolean onSingleTapUp(MotionEvent e) { 105 | return false; 106 | } 107 | 108 | @Override 109 | public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { 110 | boolean needsInvalidate = false; 111 | 112 | mIsScrolling = true; 113 | 114 | scrollTo(getScrollX() + (int) distanceX, getScrollY() + (int) distanceY); 115 | 116 | if (getScaledWidth() > getWidth()) { 117 | if (getScrollX() == getMinimalScrollX()) { 118 | needsInvalidate |= mEdgeEffectLeft.onPull(Math.abs(distanceX) / getWidth()); 119 | 120 | mEdgeEffectLeftActive = true; 121 | } else if (getScrollX() == getMaximalScrollX()) { 122 | needsInvalidate |= mEdgeEffectRight.onPull(Math.abs(distanceX) / getWidth()); 123 | 124 | mEdgeEffectRightActive = true; 125 | } 126 | } 127 | 128 | if (getScaledHeight() > getHeight()) { 129 | if (getScrollY() == getMinimalScrollY()) { 130 | needsInvalidate |= mEdgeEffectTop.onPull(Math.abs(distanceY) / getHeight()); 131 | 132 | mEdgeEffectTopActive = true; 133 | } else if (getScrollY() == getMaximalScrollY()) { 134 | needsInvalidate |= mEdgeEffectBottom.onPull(Math.abs(distanceY) / getHeight()); 135 | 136 | mEdgeEffectBottomActive = true; 137 | } 138 | } 139 | 140 | if (needsInvalidate) { 141 | ViewCompat.postInvalidateOnAnimation(this); 142 | } 143 | 144 | return true; 145 | } 146 | 147 | @Override 148 | public void onLongPress(MotionEvent e) { 149 | } 150 | 151 | @Override 152 | public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { 153 | mEdgeEffectLeft.onRelease(); 154 | mEdgeEffectTop.onRelease(); 155 | mEdgeEffectRight.onRelease(); 156 | mEdgeEffectBottom.onRelease(); 157 | 158 | mOverScroller.forceFinished(true); 159 | 160 | mOverScroller.fling(getScrollX(), getScrollY(), (int) -velocityX, (int) -velocityY, 161 | getMinimalScrollX(), getMaximalScrollX(), getMinimalScrollY(), getMaximalScrollY(), 162 | getWidth() / 2, getHeight() / 2); 163 | 164 | ViewCompat.postInvalidateOnAnimation(this); 165 | 166 | return true; 167 | } 168 | 169 | @Override 170 | public boolean onScale(ScaleGestureDetector detector) { 171 | zoomTo((int) detector.getFocusX(), (int) detector.getFocusY(), 172 | clamp(mMinZoom, mZoom * detector.getScaleFactor(), mMaxZoom)); 173 | 174 | return true; 175 | } 176 | 177 | @Override 178 | public boolean onScaleBegin(ScaleGestureDetector detector) { 179 | return true; 180 | } 181 | 182 | public void zoomTo(int x, int y, float zoom) { 183 | if (mZoom != zoom) { 184 | float zoomRatio = zoom / mZoom; 185 | int oldX = getScrollX() - getMinimalScrollX() + x; 186 | int oldY = getScrollY() - getMinimalScrollY() + y; 187 | 188 | mZoom = clamp(mMinZoom, zoom, mMaxZoom); 189 | 190 | scrollTo(getMinimalScrollX() + Math.round(oldX * zoomRatio) - x, 191 | getMinimalScrollY() + Math.round(oldY * zoomRatio) - y); 192 | 193 | ViewCompat.postInvalidateOnAnimation(this); 194 | } 195 | } 196 | 197 | @Override 198 | public void onScaleEnd(ScaleGestureDetector detector) { 199 | } 200 | 201 | @Override 202 | protected void onLayout(boolean changed, int l, int t, int r, int b) { 203 | measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); 204 | 205 | boolean isMinZoom = mMinZoom == mZoom; 206 | 207 | mMinZoom = getMinimalZoom(); 208 | 209 | mMaxZoom = mMinZoom > DEFAULT_MAX_ZOOM ? mMinZoom : DEFAULT_MAX_ZOOM; 210 | 211 | mZoom = isMinZoom ? mMinZoom : clamp(mMinZoom, mZoom, mMaxZoom); 212 | 213 | if (mZoom > 0f) { 214 | if (getScrollX() < getMinimalScrollX()) { 215 | scrollTo(getMinimalScrollX(), getScrollY()); 216 | } else if (getScrollX() > getMaximalScrollX()) { 217 | scrollTo(getMaximalScrollX(), getScrollY()); 218 | } 219 | 220 | if (getScrollY() < getMinimalScrollY()) { 221 | scrollTo(getScrollX(), getMinimalScrollY()); 222 | } else if (getScrollY() > getMaximalScrollY()) { 223 | scrollTo(getScrollX(), getMaximalScrollY()); 224 | } 225 | } 226 | 227 | super.onLayout(changed, l, t, r, b); 228 | } 229 | 230 | @Override 231 | protected void dispatchDraw(Canvas canvas) { 232 | super.dispatchDraw(canvas); 233 | 234 | final int overScrollMode = ViewCompat.getOverScrollMode(this); 235 | if (overScrollMode == ViewCompat.OVER_SCROLL_ALWAYS || 236 | overScrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS) { 237 | if (!mEdgeEffectLeft.isFinished()) { 238 | final int restoreCount = canvas.save(); 239 | final int height = getHeight() - getPaddingTop() - getPaddingBottom(); 240 | final int width = getWidth(); 241 | 242 | canvas.rotate(270); 243 | canvas.translate(-height + getPaddingTop() - getScrollY(), getMinimalScrollX()); 244 | mEdgeEffectLeft.setSize(height, width); 245 | mEdgeEffectLeft.draw(canvas); 246 | canvas.restoreToCount(restoreCount); 247 | } 248 | if (!mEdgeEffectRight.isFinished()) { 249 | final int restoreCount = canvas.save(); 250 | final int width = getWidth(); 251 | final int height = getHeight() - getPaddingTop() - getPaddingBottom(); 252 | 253 | canvas.rotate(90); 254 | canvas.translate(-getPaddingTop() + getScrollY(), -getMaximalScrollX() - getWidth()); 255 | mEdgeEffectRight.setSize(height, width); 256 | mEdgeEffectRight.draw(canvas); 257 | canvas.restoreToCount(restoreCount); 258 | } 259 | if (!mEdgeEffectTop.isFinished()) { 260 | final int restoreCount = canvas.save(); 261 | final int height = getWidth() - getPaddingLeft() - getPaddingRight(); 262 | final int width = getHeight(); 263 | 264 | canvas.translate(getPaddingLeft() + getScrollX(), getMinimalScrollY()); 265 | mEdgeEffectTop.setSize(height, width); 266 | mEdgeEffectTop.draw(canvas); 267 | canvas.restoreToCount(restoreCount); 268 | } 269 | if (!mEdgeEffectBottom.isFinished()) { 270 | final int restoreCount = canvas.save(); 271 | final int height = getWidth() - getPaddingLeft() - getPaddingRight(); 272 | final int width = getHeight(); 273 | 274 | canvas.rotate(180); 275 | canvas.translate(-getWidth() + getPaddingLeft() - getScrollX(), 276 | -getMaximalScrollY() - getHeight()); 277 | mEdgeEffectBottom.setSize(height, width); 278 | mEdgeEffectBottom.draw(canvas); 279 | canvas.restoreToCount(restoreCount); 280 | } 281 | } else { 282 | mEdgeEffectLeft.finish(); 283 | mEdgeEffectRight.finish(); 284 | mEdgeEffectTop.finish(); 285 | mEdgeEffectBottom.finish(); 286 | } 287 | 288 | if (!mEdgeEffectLeft.isFinished() || !mEdgeEffectRight.isFinished() || 289 | !mEdgeEffectTop.isFinished() || !mEdgeEffectBottom.isFinished()) { 290 | ViewCompat.postInvalidateOnAnimation(this); 291 | } 292 | } 293 | 294 | private int getMinimalScrollX() { 295 | return Math.min((getWidth() - getScaledWidth()) / 2, 0); 296 | } 297 | 298 | private int getMinimalScrollY() { 299 | return Math.min((getHeight() - getScaledHeight()) / 2, 0); 300 | } 301 | 302 | private int getMaximalScrollX() { 303 | return getMinimalScrollX() + getScaledWidth() - getWidth(); 304 | } 305 | 306 | private int getMaximalScrollY() { 307 | return getMinimalScrollY() + getScaledHeight() - getHeight(); 308 | } 309 | 310 | @Override 311 | public void scrollTo(int x, int y) { 312 | super.scrollTo(clamp(getMinimalScrollX(), x, getMaximalScrollX()), 313 | clamp(getMinimalScrollY(), y, getMaximalScrollY())); 314 | } 315 | 316 | protected int getScaledWidth() { 317 | return Math.round(getMeasuredWidth() * mZoom); 318 | } 319 | 320 | protected int getScaledHeight() { 321 | return Math.round(getMeasuredHeight() * mZoom); 322 | } 323 | 324 | @Override 325 | protected int computeHorizontalScrollExtent() { 326 | return getWidth(); 327 | } 328 | 329 | @Override 330 | protected int computeHorizontalScrollOffset() { 331 | return getScrollX() - getMinimalScrollX(); 332 | } 333 | 334 | @Override 335 | protected int computeHorizontalScrollRange() { 336 | return getScaledWidth(); 337 | } 338 | 339 | @Override 340 | protected int computeVerticalScrollExtent() { 341 | return getHeight(); 342 | } 343 | 344 | @Override 345 | protected int computeVerticalScrollOffset() { 346 | return getScrollY() - getMinimalScrollY(); 347 | } 348 | 349 | @Override 350 | protected int computeVerticalScrollRange() { 351 | return getScaledHeight(); 352 | } 353 | 354 | @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) 355 | @Override 356 | public void computeScroll() { 357 | boolean needsInvalidate = false; 358 | 359 | if (mOverScroller.computeScrollOffset()) { 360 | scrollTo(mOverScroller.getCurrX(), mOverScroller.getCurrY()); 361 | 362 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { 363 | if (getScaledWidth() > getWidth()) { 364 | if (getScrollX() == getMinimalScrollX() && mEdgeEffectLeft.isFinished() && 365 | !mEdgeEffectLeftActive) { 366 | needsInvalidate |= mEdgeEffectLeft.onAbsorb( 367 | (int) mOverScroller.getCurrVelocity()); 368 | 369 | mEdgeEffectLeftActive = true; 370 | } else if (getScrollX() == getMaximalScrollX() && 371 | mEdgeEffectRight.isFinished() && !mEdgeEffectRightActive) { 372 | needsInvalidate |= mEdgeEffectRight.onAbsorb( 373 | (int) mOverScroller.getCurrVelocity()); 374 | 375 | mEdgeEffectRightActive = true; 376 | } 377 | } 378 | 379 | if (getScaledHeight() > getHeight()) { 380 | if (getScrollY() == getMinimalScrollY() && mEdgeEffectTop.isFinished() && 381 | !mEdgeEffectTopActive) { 382 | needsInvalidate |= mEdgeEffectTop.onAbsorb( 383 | (int) mOverScroller.getCurrVelocity()); 384 | 385 | mEdgeEffectTopActive = true; 386 | } else if (getScrollY() == getMaximalScrollY() && 387 | mEdgeEffectBottom.isFinished() && !mEdgeEffectBottomActive) { 388 | needsInvalidate |= mEdgeEffectBottom.onAbsorb( 389 | (int) mOverScroller.getCurrVelocity()); 390 | 391 | mEdgeEffectBottomActive = true; 392 | } 393 | } 394 | } 395 | } 396 | 397 | if (mZoomer.computeZoom()) { 398 | zoomTo(mZoomFocalPoint.x, mZoomFocalPoint.y, mStartZoom + mZoomer.getCurrZoom()); 399 | needsInvalidate = true; 400 | } 401 | 402 | if (needsInvalidate) { 403 | ViewCompat.postInvalidateOnAnimation(this); 404 | } 405 | } 406 | 407 | public float getMinimalZoom() { 408 | return Math.min(getWidth() / getMeasuredWidth(), getHeight() / getMeasuredHeight()); 409 | } 410 | 411 | public float getZoom() { 412 | return mZoom; 413 | } 414 | 415 | public float getMaximalZoom() { 416 | return mMaxZoom; 417 | } 418 | 419 | @Override 420 | public boolean dispatchTouchEvent(MotionEvent event) { 421 | if (isEnabled() && mScaleDetector.onTouchEvent(event) && !mScaleDetector.isInProgress()) { 422 | mGestureDetector.onTouchEvent(event); 423 | } 424 | 425 | switch (event.getActionMasked()) { 426 | case MotionEvent.ACTION_CANCEL: 427 | case MotionEvent.ACTION_UP: 428 | mIsScrolling = false; 429 | break; 430 | } 431 | 432 | return isEnabled() && super.dispatchTouchEvent(event); 433 | } 434 | 435 | @Override 436 | public boolean onInterceptTouchEvent(MotionEvent event) { 437 | return isEnabled() && (mScaleDetector.isInProgress() || mIsScrolling); 438 | } 439 | 440 | private float clamp(float min, float val, float max) { 441 | if (Float.isNaN(min)) min = 0f; 442 | if (Float.isNaN(val)) val = 0f; 443 | if (Float.isNaN(max)) max = 0f; 444 | 445 | return Math.max(min, Math.min(val, max)); 446 | } 447 | 448 | private int clamp(int min, int val, int max) { 449 | return Math.max(min, Math.min(val, max)); 450 | } 451 | 452 | @Override 453 | public boolean onSingleTapConfirmed(MotionEvent e) { 454 | return false; 455 | } 456 | 457 | @Override 458 | public boolean onDoubleTap(MotionEvent e) { 459 | mZoomer.forceFinished(true); 460 | 461 | mZoomFocalPoint.x = (int) e.getX(); 462 | mZoomFocalPoint.y = (int) e.getY(); 463 | 464 | mStartZoom = mZoom; 465 | 466 | mZoomer.startZoom(mZoom > mMinZoom + 0.001f ? -(mZoom - mMinZoom) : mMaxZoom - mZoom); 467 | 468 | ViewCompat.postInvalidateOnAnimation(this); 469 | 470 | return true; 471 | } 472 | 473 | @Override 474 | public boolean onDoubleTapEvent(MotionEvent e) { 475 | return false; 476 | } 477 | 478 | public boolean isScrolling() { 479 | return mIsScrolling; 480 | } 481 | } 482 | --------------------------------------------------------------------------------