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