diff options
| author | Bananeweizen <bananeweizen@gmx.de> | 2014-08-14 22:28:19 +0200 |
|---|---|---|
| committer | Bananeweizen <bananeweizen@gmx.de> | 2014-08-14 22:28:19 +0200 |
| commit | cfd75411787da9809a4f7e4e5e1f0be6a9a0fa77 (patch) | |
| tree | 94687c96ea7cb15507e7310e419e371071d219ae | |
| parent | 8c468294662cb57a1d7b8ec8b9a9195e277bc339 (diff) | |
| download | cgeo-cfd75411787da9809a4f7e4e5e1f0be6a9a0fa77.zip cgeo-cfd75411787da9809a4f7e4e5e1f0be6a9a0fa77.tar.gz cgeo-cfd75411787da9809a4f7e4e5e1f0be6a9a0fa77.tar.bz2 | |
initial showcase implementation
59 files changed, 2644 insertions, 15 deletions
diff --git a/main/project.properties b/main/project.properties index f0fa845..6348c7d 100644 --- a/main/project.properties +++ b/main/project.properties @@ -15,4 +15,5 @@ target=Google Inc.:Google APIs:19 android.library.reference.1=../mapswithme-api android.library.reference.2=../android-support-v7-appcompat java.source=1.7 -java.target=1.7
\ No newline at end of file +java.target=1.7 +android.library.reference.3=../../../workspaces/cgeo/showcaseview diff --git a/main/res/values/showcaseview_styles.xml b/main/res/values/showcaseview_styles.xml new file mode 100644 index 0000000..864982f --- /dev/null +++ b/main/res/values/showcaseview_styles.xml @@ -0,0 +1,61 @@ +<!-- + ~ Copyright 2014 Alex Curran + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources> + + <style name="ShowcaseButton"> + <item name="android:layout_width">wrap_content</item> + <item name="android:layout_height">wrap_content</item> + <item name="android:minWidth">96dp</item> + <item name="android:gravity">center</item> + <item name="android:textStyle">bold</item> + <item name="android:textColor">#FFFFFF</item> + <item name="android:background">@drawable/button</item> + </style> + + <style name="ShowcaseView.Light"> + <item name="sv_titleTextAppearance">@style/TextAppearance.ShowcaseView.Title</item> + <item name="sv_detailTextAppearance">@style/TextAppearance.ShowcaseView.Detail.Light</item> + <item name="sv_backgroundColor">#3333B5E5</item> + <item name="sv_buttonText">@string/ok</item> + </style> + + <style name="ShowcaseView"> + <item name="sv_titleTextAppearance">@style/TextAppearance.ShowcaseView.Title</item> + <item name="sv_detailTextAppearance">@style/TextAppearance.ShowcaseView.Detail</item> + <item name="sv_backgroundColor">#EE223344</item> + <item name="sv_buttonText">@string/ok</item> + </style> + + <style name="TextAppearance.ShowcaseView.Title" parent="android:style/TextAppearance.Large"> + <item name="android:textColor">#33B5E5</item> + </style> + + <style name="TextAppearance.ShowcaseView.Detail" parent="android:style/TextAppearance"> + <item name="android:textColor">#FFFFFF</item> + </style> + + <style name="TextAppearance.ShowcaseView.Detail.Light"> + <item name="android:textColor">#333333</item> + <item name="android:shadowColor">#FF0000</item> + <item name="android:shadowRadius">0</item> + </style> + + <style name="TextAppearance.ShowcaseView.Title.Light"> + <item name="android:shadowRadius">0</item> + </style> + +</resources>
\ No newline at end of file diff --git a/main/res/values/strings.xml b/main/res/values/strings.xml index 28d5d80..14c1fbe 100644 --- a/main/res/values/strings.xml +++ b/main/res/values/strings.xml @@ -1269,5 +1269,12 @@ <string name="cgeo_shortcut">c:geo shortcut</string> <string name="create_shortcut">Create shortcut</string> <string name="send">Send</string> - + + <string name="showcase_logcache_title">Sending the log</string> + <string name="showcase_logcache_text">Remember, many commands are now in the action bar. There you find the button for sending the finished log.</string> + <string name="showcase_main_title">Action bar</string> + <string name="showcase_main_text">c:geo now uses the Android action bar at the top for menu items. More menu items are hidden behind the dotted symbol. Long press an action bar item to see its name.</string> + <string name="showcase_search_title"></string> + <string name="showcase_search_text"></string> + </resources> diff --git a/main/src/cgeo/geocaching/LogCacheActivity.java b/main/src/cgeo/geocaching/LogCacheActivity.java index 83bbdf2..9d2fa68 100644 --- a/main/src/cgeo/geocaching/LogCacheActivity.java +++ b/main/src/cgeo/geocaching/LogCacheActivity.java @@ -2,6 +2,7 @@ package cgeo.geocaching; import butterknife.ButterKnife; +import cgeo.geocaching.activity.ShowcaseViewBuilder; import cgeo.geocaching.connector.ILoggingManager; import cgeo.geocaching.connector.ImageResult; import cgeo.geocaching.connector.LogResult; @@ -21,6 +22,8 @@ import cgeo.geocaching.utils.Log; import cgeo.geocaching.utils.LogTemplateProvider; import cgeo.geocaching.utils.LogTemplateProvider.LogContext; +import com.github.amlcurran.showcaseview.targets.ActionItemTarget; + import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; @@ -638,7 +641,14 @@ public class LogCacheActivity extends AbstractLoggingActivity implements DateDia menu.findItem(R.id.menu_image).setVisible(cache.supportsLogImages()); menu.findItem(R.id.save).setVisible(true); menu.findItem(R.id.clear).setVisible(true); + presentShowcase(); return true; } + @Override + protected ShowcaseViewBuilder getShowcase() { + return new ShowcaseViewBuilder(this) + .setTarget(new ActionItemTarget(this, R.id.menu_send)) + .setContent(R.string.showcase_logcache_title, R.string.showcase_logcache_text); + } } diff --git a/main/src/cgeo/geocaching/MainActivity.java b/main/src/cgeo/geocaching/MainActivity.java index 2d6e9f0..ed58875 100644 --- a/main/src/cgeo/geocaching/MainActivity.java +++ b/main/src/cgeo/geocaching/MainActivity.java @@ -4,6 +4,7 @@ import butterknife.ButterKnife; import butterknife.InjectView; import cgeo.geocaching.activity.AbstractActionBarActivity; +import cgeo.geocaching.activity.ShowcaseViewBuilder; import cgeo.geocaching.connector.ConnectorFactory; import cgeo.geocaching.connector.capability.ILogin; import cgeo.geocaching.connector.gc.GCConnector; @@ -27,6 +28,7 @@ import cgeo.geocaching.utils.RxUtils; import cgeo.geocaching.utils.TextUtils; import cgeo.geocaching.utils.Version; +import com.github.amlcurran.showcaseview.targets.ActionViewTarget; import com.google.zxing.integration.android.IntentIntegrator; import com.google.zxing.integration.android.IntentResult; @@ -284,7 +286,7 @@ public class MainActivity extends AbstractActionBarActivity { final MenuItem searchItem = menu.findItem(R.id.menu_gosearch); final SearchView searchView = (SearchView) MenuItemCompat.getActionView(searchItem); searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName())); - + presentShowcase(); return true; } @@ -749,4 +751,11 @@ public class MainActivity extends AbstractActionBarActivity { public void showAbout(final View view) { startActivity(new Intent(this, AboutActivity.class)); } + + @Override + protected ShowcaseViewBuilder getShowcase() { + return new ShowcaseViewBuilder(this) + .setTarget(new ActionViewTarget(this, ActionViewTarget.Type.OVERFLOW)) + .setContent(R.string.showcase_main_title, R.string.showcase_main_text); + } } diff --git a/main/src/cgeo/geocaching/SearchActivity.java b/main/src/cgeo/geocaching/SearchActivity.java index 81dec98..6a4453c 100644 --- a/main/src/cgeo/geocaching/SearchActivity.java +++ b/main/src/cgeo/geocaching/SearchActivity.java @@ -177,14 +177,14 @@ public class SearchActivity extends AbstractActionBarActivity implements Coordin buttonLatitude.setOnClickListener(new OnClickListener() { @Override - public void onClick(View v) { + public void onClick(final View v) { updateCoordinates(); } }); buttonLongitude.setOnClickListener(new OnClickListener() { @Override - public void onClick(View v) { + public void onClick(final View v) { updateCoordinates(); } }); @@ -398,6 +398,7 @@ public class SearchActivity extends AbstractActionBarActivity implements Coordin @Override public final boolean onCreateOptionsMenu(final Menu menu) { getMenuInflater().inflate(R.menu.search_activity_options, menu); + presentShowcase(); return true; } @@ -417,4 +418,15 @@ public class SearchActivity extends AbstractActionBarActivity implements Coordin putExtra(Intents.EXTRA_KEYWORD_SEARCH, false); fromActivity.startActivityForResult(searchIntent, MainActivity.SEARCH_REQUEST_CODE); } + + // The showcase doesn't work well with the search activity, because on searching a geocode (or + // selecting a cache from the search field) we immediately close the activity. That in turn confuses the delayed + // creation of the showcase bitmap. + // + // @Override + // protected ShowcaseViewBuilder getShowcase() { + // return new ShowcaseViewBuilder(this) + // .setTarget(new ActionItemTarget(this, R.id.menu_search_own_caches)) + // .setContent(R.string.showcase_search_title, R.string.showcase_search_text); + // } } diff --git a/main/src/cgeo/geocaching/activity/AbstractActivity.java b/main/src/cgeo/geocaching/activity/AbstractActivity.java index e3df1f7..442217d 100644 --- a/main/src/cgeo/geocaching/activity/AbstractActivity.java +++ b/main/src/cgeo/geocaching/activity/AbstractActivity.java @@ -60,26 +60,49 @@ public abstract class AbstractActivity extends ActionBarActivity implements IAbs } @Override - public final void showToast(String text) { + public final void showToast(final String text) { ActivityMixin.showToast(this, text); } @Override - public final void showShortToast(String text) { + public final void showShortToast(final String text) { ActivityMixin.showShortToast(this, text); } @Override - public void onCreate(Bundle savedInstanceState) { + public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); supportRequestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); initializeCommonFields(); + } + /** + * Call this method to actually present a showcase. The right time to invoke this method depends on the showcase + * target. I.e. if the showcase target is an action bar item, this method can only be invoked after that item has + * been created in onCreateOptionsMenu. + */ + protected final void presentShowcase() { + final ShowcaseViewBuilder builder = getShowcase(); + if (builder != null) { + builder.setStyle(R.style.ShowcaseView); + builder.build(); + } + } + + /** + * Override this method to create a showcase view highlighting the most important UI element. + * + * @return + */ + @SuppressWarnings("static-method") + protected ShowcaseViewBuilder getShowcase() { + // do nothing by default + return null; } @Override - public boolean onOptionsItemSelected(MenuItem item) { + public boolean onOptionsItemSelected(final MenuItem item) { if (item.getItemId() == android.R.id.home) { return ActivityMixin.navigateUp(this); } @@ -116,7 +139,6 @@ public abstract class AbstractActivity extends ActionBarActivity implements IAbs } protected void onCreate(final Bundle savedInstanceState, final int resourceLayoutID) { - super.onCreate(savedInstanceState); initializeCommonFields(); @@ -141,7 +163,7 @@ public abstract class AbstractActivity extends ActionBarActivity implements IAbs } @Override - public void setContentView(int layoutResID) { + public void setContentView(final int layoutResID) { super.setContentView(layoutResID); // initialize the action bar title with the activity title for single source @@ -206,7 +228,7 @@ public abstract class AbstractActivity extends ActionBarActivity implements IAbs public String getUri(); } - protected void initializeAndroidBeam(ActivitySharingInterface sharingInterface) { + protected void initializeAndroidBeam(final ActivitySharingInterface sharingInterface) { if (Build.VERSION.SDK_INT > Build.VERSION_CODES.ICE_CREAM_SANDWICH) { initializeICSAndroidBeam(sharingInterface); } @@ -214,14 +236,14 @@ public abstract class AbstractActivity extends ActionBarActivity implements IAbs @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) protected void initializeICSAndroidBeam(final ActivitySharingInterface sharingInterface) { - NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this); + final NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this); if (nfcAdapter == null) { return; } nfcAdapter.setNdefPushMessageCallback(new NfcAdapter.CreateNdefMessageCallback() { @Override - public NdefMessage createNdefMessage(NfcEvent event) { - NdefRecord record = NdefRecord.createUri(sharingInterface.getUri()); + public NdefMessage createNdefMessage(final NfcEvent event) { + final NdefRecord record = NdefRecord.createUri(sharingInterface.getUri()); return new NdefMessage(new NdefRecord[]{record}); } }, this); diff --git a/main/src/cgeo/geocaching/activity/ShowcaseViewBuilder.java b/main/src/cgeo/geocaching/activity/ShowcaseViewBuilder.java new file mode 100644 index 0000000..2d34b34 --- /dev/null +++ b/main/src/cgeo/geocaching/activity/ShowcaseViewBuilder.java @@ -0,0 +1,58 @@ +package cgeo.geocaching.activity; + +import com.github.amlcurran.showcaseview.ShowcaseView.Builder; +import com.github.amlcurran.showcaseview.targets.Target; + +import android.app.Activity; + +public class ShowcaseViewBuilder extends Builder { + + private final Activity activity; + + public ShowcaseViewBuilder(final Activity activity) { + super(activity); + this.activity = activity; + } + + @Override + public ShowcaseViewBuilder setContentTitle(final int resId) { + setSingleshot(activity.getResources().getString(resId)); + return (ShowcaseViewBuilder) super.setContentTitle(resId); + } + + /** + * Use the hash of the title for the single shot remembering + * + * @param resId + */ + private void setSingleshot(final CharSequence title) { + super.singleShot(title.hashCode()); + } + + @Override + public ShowcaseViewBuilder setContentText(final int resId) { + return (ShowcaseViewBuilder) super.setContentText(resId); + } + + @Override + public ShowcaseViewBuilder setContentText(final CharSequence text) { + return (ShowcaseViewBuilder) super.setContentText(text); + } + + @Override + public ShowcaseViewBuilder setContentTitle(final CharSequence title) { + setSingleshot(title); + return (ShowcaseViewBuilder) super.setContentTitle(title); + } + + @Override + public ShowcaseViewBuilder setTarget(final Target target) { + return (ShowcaseViewBuilder) super.setTarget(target); + } + + public ShowcaseViewBuilder setContent(final int titleId, final int textId) { + setContentTitle(titleId); + return setContentText(textId); + } + +} diff --git a/showcaseview/.classpath b/showcaseview/.classpath new file mode 100644 index 0000000..be2dd15 --- /dev/null +++ b/showcaseview/.classpath @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="UTF-8"?> +<classpath> + <classpathentry kind="src" path="gen"/> + <classpathentry kind="src" path="java"/> + <classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/> + <classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/> + <classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.DEPENDENCIES"/> + <classpathentry kind="output" path="bin/classes"/> +</classpath> diff --git a/showcaseview/.project b/showcaseview/.project new file mode 100644 index 0000000..5be774b --- /dev/null +++ b/showcaseview/.project @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="UTF-8"?> +<projectDescription> + <name>showcaseview</name> + <comment></comment> + <projects> + </projects> + <buildSpec> + <buildCommand> + <name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name> + <arguments> + </arguments> + </buildCommand> + <buildCommand> + <name>com.android.ide.eclipse.adt.PreCompilerBuilder</name> + <arguments> + </arguments> + </buildCommand> + <buildCommand> + <name>org.eclipse.jdt.core.javabuilder</name> + <arguments> + </arguments> + </buildCommand> + <buildCommand> + <name>com.android.ide.eclipse.adt.ApkBuilder</name> + <arguments> + </arguments> + </buildCommand> + </buildSpec> + <natures> + <nature>com.android.ide.eclipse.adt.AndroidNature</nature> + <nature>org.eclipse.jdt.core.javanature</nature> + </natures> +</projectDescription> diff --git a/showcaseview/.settings/org.eclipse.jdt.core.prefs b/showcaseview/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..b080d2d --- /dev/null +++ b/showcaseview/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,4 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 +org.eclipse.jdt.core.compiler.compliance=1.6 +org.eclipse.jdt.core.compiler.source=1.6 diff --git a/showcaseview/AndroidManifest.xml b/showcaseview/AndroidManifest.xml new file mode 100644 index 0000000..28494cf --- /dev/null +++ b/showcaseview/AndroidManifest.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright 2014 Alex Curran + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.github.amlcurran.showcaseview"> + <uses-sdk + android:minSdkVersion="4" + android:targetSdkVersion="19" /> + +</manifest> diff --git a/showcaseview/java/com/github/amlcurran/showcaseview/AnimationFactory.java b/showcaseview/java/com/github/amlcurran/showcaseview/AnimationFactory.java new file mode 100644 index 0000000..dac6a00 --- /dev/null +++ b/showcaseview/java/com/github/amlcurran/showcaseview/AnimationFactory.java @@ -0,0 +1,36 @@ +/* + * Copyright 2014 Alex Curran + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.github.amlcurran.showcaseview; + +import android.graphics.Point; +import android.view.View; + +interface AnimationFactory { + void fadeInView(View target, long duration, AnimationStartListener listener); + + void fadeOutView(View target, long duration, AnimationEndListener listener); + + void animateTargetToPoint(ShowcaseView showcaseView, Point point); + + public interface AnimationStartListener { + void onAnimationStart(); + } + + public interface AnimationEndListener { + void onAnimationEnd(); + } +} diff --git a/showcaseview/java/com/github/amlcurran/showcaseview/AnimatorAnimationFactory.java b/showcaseview/java/com/github/amlcurran/showcaseview/AnimatorAnimationFactory.java new file mode 100644 index 0000000..bbc24b7 --- /dev/null +++ b/showcaseview/java/com/github/amlcurran/showcaseview/AnimatorAnimationFactory.java @@ -0,0 +1,96 @@ +/* + * Copyright 2014 Alex Curran + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.github.amlcurran.showcaseview; + +import android.animation.Animator; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.graphics.Point; +import android.view.View; +import android.view.animation.AccelerateDecelerateInterpolator; + +class AnimatorAnimationFactory implements AnimationFactory { + + private static final String ALPHA = "alpha"; + private static final float INVISIBLE = 0f; + private static final float VISIBLE = 1f; + + private final AccelerateDecelerateInterpolator interpolator; + + public AnimatorAnimationFactory() { + interpolator = new AccelerateDecelerateInterpolator(); + } + + @Override + public void fadeInView(View target, long duration, final AnimationStartListener listener) { + ObjectAnimator oa = ObjectAnimator.ofFloat(target, ALPHA, INVISIBLE, VISIBLE); + oa.setDuration(duration).addListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animator) { + listener.onAnimationStart(); + } + + @Override + public void onAnimationEnd(Animator animator) { + } + + @Override + public void onAnimationCancel(Animator animator) { + } + + @Override + public void onAnimationRepeat(Animator animator) { + } + }); + oa.start(); + } + + @Override + public void fadeOutView(View target, long duration, final AnimationEndListener listener) { + ObjectAnimator oa = ObjectAnimator.ofFloat(target, ALPHA, INVISIBLE); + oa.setDuration(duration).addListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animator) { + } + + @Override + public void onAnimationEnd(Animator animator) { + listener.onAnimationEnd(); + } + + @Override + public void onAnimationCancel(Animator animator) { + } + + @Override + public void onAnimationRepeat(Animator animator) { + } + }); + oa.start(); + } + + @Override + public void animateTargetToPoint(ShowcaseView showcaseView, Point point) { + AnimatorSet set = new AnimatorSet(); + ObjectAnimator xAnimator = ObjectAnimator.ofInt(showcaseView, "showcaseX", point.x); + ObjectAnimator yAnimator = ObjectAnimator.ofInt(showcaseView, "showcaseY", point.y); + set.playTogether(xAnimator, yAnimator); + set.setInterpolator(interpolator); + set.start(); + } + +} diff --git a/showcaseview/java/com/github/amlcurran/showcaseview/ApiUtils.java b/showcaseview/java/com/github/amlcurran/showcaseview/ApiUtils.java new file mode 100644 index 0000000..2e94e1d --- /dev/null +++ b/showcaseview/java/com/github/amlcurran/showcaseview/ApiUtils.java @@ -0,0 +1,39 @@ +/* + * Copyright 2014 Alex Curran + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.github.amlcurran.showcaseview; + +import android.annotation.TargetApi; +import android.os.Build; +import android.view.View; + +public class ApiUtils { + + public boolean isCompatWith(int versionCode) { + return Build.VERSION.SDK_INT >= versionCode; + } + + public boolean isCompatWithHoneycomb() { + return isCompatWith(Build.VERSION_CODES.HONEYCOMB); + } + + @TargetApi(14) + public void setFitsSystemWindowsCompat(View view) { + if (isCompatWith(Build.VERSION_CODES.ICE_CREAM_SANDWICH)) { + view.setFitsSystemWindows(true); + } + } +} diff --git a/showcaseview/java/com/github/amlcurran/showcaseview/Calculator.java b/showcaseview/java/com/github/amlcurran/showcaseview/Calculator.java new file mode 100644 index 0000000..78fba6f --- /dev/null +++ b/showcaseview/java/com/github/amlcurran/showcaseview/Calculator.java @@ -0,0 +1,34 @@ +/* + * Copyright 2014 Alex Curran + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.github.amlcurran.showcaseview; + +import android.graphics.Point; +import android.view.View; + +/** + * Calculates various items for use with ShowcaseView + */ +class Calculator { + + static Point getShowcasePointFromView(View view) { + Point result = new Point(); + result.x = view.getLeft() + view.getWidth() / 2; + result.y = view.getTop() + view.getHeight() / 2; + return result; + } + +} diff --git a/showcaseview/java/com/github/amlcurran/showcaseview/NewShowcaseDrawer.java b/showcaseview/java/com/github/amlcurran/showcaseview/NewShowcaseDrawer.java new file mode 100644 index 0000000..8d16b86 --- /dev/null +++ b/showcaseview/java/com/github/amlcurran/showcaseview/NewShowcaseDrawer.java @@ -0,0 +1,71 @@ +/* + * Copyright 2014 Alex Curran + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.github.amlcurran.showcaseview; + +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.Canvas; + +/** + * Created by curraa01 on 13/10/2013. + */ +class NewShowcaseDrawer extends StandardShowcaseDrawer { + + private static final int ALPHA_60_PERCENT = 153; + private final float outerRadius; + private final float innerRadius; + + public NewShowcaseDrawer(Resources resources) { + super(resources); + outerRadius = resources.getDimension(R.dimen.showcase_radius_outer); + innerRadius = resources.getDimension(R.dimen.showcase_radius_inner); + } + + @Override + public void setShowcaseColour(int color) { + eraserPaint.setColor(color); + } + + @Override + public void drawShowcase(Bitmap buffer, float x, float y, float scaleMultiplier) { + Canvas bufferCanvas = new Canvas(buffer); + eraserPaint.setAlpha(ALPHA_60_PERCENT); + bufferCanvas.drawCircle(x, y, outerRadius, eraserPaint); + eraserPaint.setAlpha(0); + bufferCanvas.drawCircle(x, y, innerRadius, eraserPaint); + } + + @Override + public int getShowcaseWidth() { + return (int) (outerRadius * 2); + } + + @Override + public int getShowcaseHeight() { + return (int) (outerRadius * 2); + } + + @Override + public float getBlockedRadius() { + return innerRadius; + } + + @Override + public void setBackgroundColour(int backgroundColor) { + this.backgroundColour = backgroundColor; + } +} diff --git a/showcaseview/java/com/github/amlcurran/showcaseview/OnShowcaseEventListener.java b/showcaseview/java/com/github/amlcurran/showcaseview/OnShowcaseEventListener.java new file mode 100644 index 0000000..1ff298a --- /dev/null +++ b/showcaseview/java/com/github/amlcurran/showcaseview/OnShowcaseEventListener.java @@ -0,0 +1,61 @@ +/* + * Copyright 2014 Alex Curran + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.github.amlcurran.showcaseview; + +/** +* @author Alex +*/ +public interface OnShowcaseEventListener { + + /** + * Called when the ShowcaseView has been told to hide. Use {@link #onShowcaseViewDidHide(ShowcaseView)} + * if you want to know when the ShowcaseView has been fully hidden. + */ + public void onShowcaseViewHide(ShowcaseView showcaseView); + + /** + * Called when the animation hiding the ShowcaseView has finished, and it is no longer visible on the screen. + */ + public void onShowcaseViewDidHide(ShowcaseView showcaseView); + + /** + * Called when the ShowcaseView is shown. + */ + public void onShowcaseViewShow(ShowcaseView showcaseView); + + /** + * Empty implementation of OnShowcaseViewEventListener such that null + * checks aren't needed + */ + public static final OnShowcaseEventListener NONE = new OnShowcaseEventListener() { + @Override + public void onShowcaseViewHide(ShowcaseView showcaseView) { + + } + + @Override + public void onShowcaseViewDidHide(ShowcaseView showcaseView) { + + } + + @Override + public void onShowcaseViewShow(ShowcaseView showcaseView) { + + } + }; + +} diff --git a/showcaseview/java/com/github/amlcurran/showcaseview/ShotStateStore.java b/showcaseview/java/com/github/amlcurran/showcaseview/ShotStateStore.java new file mode 100644 index 0000000..6ff782a --- /dev/null +++ b/showcaseview/java/com/github/amlcurran/showcaseview/ShotStateStore.java @@ -0,0 +1,56 @@ +/* + * Copyright 2014 Alex Curran + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.github.amlcurran.showcaseview; + +import android.content.Context; +import android.content.SharedPreferences; + +class ShotStateStore { + + private static final String PREFS_SHOWCASE_INTERNAL = "showcase_internal"; + private static final int INVALID_SHOT_ID = -1; + + long shotId = INVALID_SHOT_ID; + + private final Context context; + + public ShotStateStore(Context context) { + this.context = context; + } + + boolean hasShot() { + return isSingleShot() && context + .getSharedPreferences(PREFS_SHOWCASE_INTERNAL, Context.MODE_PRIVATE) + .getBoolean("hasShot" + shotId, false); + } + + boolean isSingleShot() { + return shotId != INVALID_SHOT_ID; + } + + void storeShot() { + if (isSingleShot()) { + SharedPreferences internal = context.getSharedPreferences(PREFS_SHOWCASE_INTERNAL, Context.MODE_PRIVATE); + internal.edit().putBoolean("hasShot" + shotId, true).apply(); + } + } + + void setSingleShot(long shotId) { + this.shotId = shotId; + } + +}
\ No newline at end of file diff --git a/showcaseview/java/com/github/amlcurran/showcaseview/ShowcaseAreaCalculator.java b/showcaseview/java/com/github/amlcurran/showcaseview/ShowcaseAreaCalculator.java new file mode 100644 index 0000000..b50633b --- /dev/null +++ b/showcaseview/java/com/github/amlcurran/showcaseview/ShowcaseAreaCalculator.java @@ -0,0 +1,60 @@ +/* + * Copyright 2014 Alex Curran + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.github.amlcurran.showcaseview; + +import android.graphics.Rect; +import android.util.Log; + +/** + * Class responsible for calculating where the Showcase should position itself + */ +class ShowcaseAreaCalculator { + + private final Rect mShowcaseRect = new Rect(); + + /** + * Creates a {@link android.graphics.Rect} which represents the area the showcase covers. Used + * to calculate where best to place the text + * + * @return true if voidedArea has changed, false otherwise. + */ + public boolean calculateShowcaseRect(float x, float y, ShowcaseDrawer showcaseDrawer) { + + int cx = (int) x, cy = (int) y; + int dw = showcaseDrawer.getShowcaseWidth(); + int dh = showcaseDrawer.getShowcaseHeight(); + + if (mShowcaseRect.left == cx - dw / 2 && mShowcaseRect.top == cy - dh / 2) { + return false; + } + + Log.d("ShowcaseView", "Recalculated"); + + mShowcaseRect.left = cx - dw / 2; + mShowcaseRect.top = cy - dh / 2; + mShowcaseRect.right = cx + dw / 2; + mShowcaseRect.bottom = cy + dh / 2; + + return true; + + } + + public Rect getShowcaseRect() { + return mShowcaseRect; + } + +} diff --git a/showcaseview/java/com/github/amlcurran/showcaseview/ShowcaseDrawer.java b/showcaseview/java/com/github/amlcurran/showcaseview/ShowcaseDrawer.java new file mode 100644 index 0000000..3cd82aa --- /dev/null +++ b/showcaseview/java/com/github/amlcurran/showcaseview/ShowcaseDrawer.java @@ -0,0 +1,42 @@ +/* + * Copyright 2014 Alex Curran + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.github.amlcurran.showcaseview; + +import android.graphics.Bitmap; +import android.graphics.Canvas; + +/** + * Created by curraa01 on 13/10/2013. + */ +interface ShowcaseDrawer { + + void setShowcaseColour(int color); + + void drawShowcase(Bitmap buffer, float x, float y, float scaleMultiplier); + + int getShowcaseWidth(); + + int getShowcaseHeight(); + + float getBlockedRadius(); + + void setBackgroundColour(int backgroundColor); + + void erase(Bitmap bitmapBuffer); + + void drawToCanvas(Canvas canvas, Bitmap bitmapBuffer); +} diff --git a/showcaseview/java/com/github/amlcurran/showcaseview/ShowcaseView.java b/showcaseview/java/com/github/amlcurran/showcaseview/ShowcaseView.java new file mode 100644 index 0000000..6c32c8d --- /dev/null +++ b/showcaseview/java/com/github/amlcurran/showcaseview/ShowcaseView.java @@ -0,0 +1,603 @@ +/* + * Copyright 2014 Alex Curran + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.github.amlcurran.showcaseview; + +import android.app.Activity; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Point; +import android.graphics.PorterDuff; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewTreeObserver; +import android.widget.Button; +import android.widget.RelativeLayout; + +import com.github.amlcurran.showcaseview.targets.Target; + +import static com.github.amlcurran.showcaseview.AnimationFactory.AnimationEndListener; +import static com.github.amlcurran.showcaseview.AnimationFactory.AnimationStartListener; + +/** + * A view which allows you to showcase areas of your app with an explanation. + */ +public class ShowcaseView extends RelativeLayout + implements View.OnClickListener, View.OnTouchListener, ViewTreeObserver.OnPreDrawListener, ViewTreeObserver.OnGlobalLayoutListener { + + private static final int HOLO_BLUE = Color.parseColor("#33B5E5"); + + private final Button mEndButton; + private final TextDrawer textDrawer; + private final ShowcaseDrawer showcaseDrawer; + private final ShowcaseAreaCalculator showcaseAreaCalculator; + private final AnimationFactory animationFactory; + private final ShotStateStore shotStateStore; + + // Showcase metrics + private int showcaseX = -1; + private int showcaseY = -1; + private float scaleMultiplier = 1f; + + // Touch items + private boolean hasCustomClickListener = false; + private boolean blockTouches = true; + private boolean hideOnTouch = false; + private OnShowcaseEventListener mEventListener = OnShowcaseEventListener.NONE; + + private boolean hasAlteredText = false; + private boolean hasNoTarget = false; + private boolean shouldCentreText; + private Bitmap bitmapBuffer; + + // Animation items + private long fadeInMillis; + private long fadeOutMillis; + + protected ShowcaseView(Context context, boolean newStyle) { + this(context, null, R.styleable.CustomTheme_showcaseViewStyle, newStyle); + } + + protected ShowcaseView(Context context, AttributeSet attrs, int defStyle, boolean newStyle) { + super(context, attrs, defStyle); + + ApiUtils apiUtils = new ApiUtils(); + animationFactory = new AnimatorAnimationFactory(); + showcaseAreaCalculator = new ShowcaseAreaCalculator(); + shotStateStore = new ShotStateStore(context); + + apiUtils.setFitsSystemWindowsCompat(this); + getViewTreeObserver().addOnPreDrawListener(this); + getViewTreeObserver().addOnGlobalLayoutListener(this); + + // Get the attributes for the ShowcaseView + final TypedArray styled = context.getTheme() + .obtainStyledAttributes(attrs, R.styleable.ShowcaseView, R.attr.showcaseViewStyle, + R.style.ShowcaseView); + + // Set the default animation times + fadeInMillis = getResources().getInteger(android.R.integer.config_mediumAnimTime); + fadeOutMillis = getResources().getInteger(android.R.integer.config_mediumAnimTime); + + mEndButton = (Button) LayoutInflater.from(context).inflate(R.layout.showcase_button, null); + if (newStyle) { + showcaseDrawer = new NewShowcaseDrawer(getResources()); + } else { + showcaseDrawer = new StandardShowcaseDrawer(getResources()); + } + textDrawer = new TextDrawer(getResources(), showcaseAreaCalculator, getContext()); + + updateStyle(styled, false); + + init(); + } + + private void init() { + + setOnTouchListener(this); + + if (mEndButton.getParent() == null) { + int margin = (int) getResources().getDimension(R.dimen.button_margin); + RelativeLayout.LayoutParams lps = (LayoutParams) generateDefaultLayoutParams(); + lps.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM); + lps.addRule(RelativeLayout.ALIGN_PARENT_RIGHT); + lps.setMargins(margin, margin, margin, margin); + mEndButton.setLayoutParams(lps); + mEndButton.setText(android.R.string.ok); + if (!hasCustomClickListener) { + mEndButton.setOnClickListener(this); + } + addView(mEndButton); + } + + } + + private boolean hasShot() { + return shotStateStore.hasShot(); + } + + void setShowcasePosition(Point point) { + setShowcasePosition(point.x, point.y); + } + + void setShowcasePosition(int x, int y) { + if (shotStateStore.hasShot()) { + return; + } + showcaseX = x; + showcaseY = y; + //init(); + invalidate(); + } + + public void setTarget(final Target target) { + setShowcase(target, false); + } + + public void setShowcase(final Target target, final boolean animate) { + postDelayed(new Runnable() { + @Override + public void run() { + + if (!shotStateStore.hasShot()) { + + updateBitmap(); + Point targetPoint = target.getPoint(); + if (targetPoint != null) { + hasNoTarget = false; + if (animate) { + animationFactory.animateTargetToPoint(ShowcaseView.this, targetPoint); + } else { + setShowcasePosition(targetPoint); + } + } else { + hasNoTarget = true; + invalidate(); + } + + } + } + }, 100); + } + + private void updateBitmap() { + if (bitmapBuffer == null || haveBoundsChanged()) { + if(bitmapBuffer != null) + bitmapBuffer.recycle(); + bitmapBuffer = Bitmap.createBitmap(getMeasuredWidth(), getMeasuredHeight(), Bitmap.Config.ARGB_8888); + + } + } + + private boolean haveBoundsChanged() { + return getMeasuredWidth() != bitmapBuffer.getWidth() || + getMeasuredHeight() != bitmapBuffer.getHeight(); + } + + public boolean hasShowcaseView() { + return (showcaseX != 1000000 && showcaseY != 1000000) && !hasNoTarget; + } + + public void setShowcaseX(int x) { + setShowcasePosition(x, showcaseY); + } + + public void setShowcaseY(int y) { + setShowcasePosition(showcaseX, y); + } + + public int getShowcaseX() { + return showcaseX; + } + + public int getShowcaseY() { + return showcaseY; + } + + /** + * Override the standard button click event + * + * @param listener Listener to listen to on click events + */ + public void overrideButtonClick(OnClickListener listener) { + if (shotStateStore.hasShot()) { + return; + } + if (mEndButton != null) { + mEndButton.setOnClickListener(listener != null ? listener : this); + } + hasCustomClickListener = true; + } + + public void setOnShowcaseEventListener(OnShowcaseEventListener listener) { + if (listener != null) { + mEventListener = listener; + } else { + mEventListener = OnShowcaseEventListener.NONE; + } + } + + public void setButtonText(CharSequence text) { + if (mEndButton != null) { + mEndButton.setText(text); + } + } + + @Override + public boolean onPreDraw() { + boolean recalculatedCling = showcaseAreaCalculator.calculateShowcaseRect(showcaseX, showcaseY, showcaseDrawer); + boolean recalculateText = recalculatedCling || hasAlteredText; + if (recalculateText) { + textDrawer.calculateTextPosition(getMeasuredWidth(), getMeasuredHeight(), this, shouldCentreText); + } + hasAlteredText = false; + return true; + } + + @Override + protected void dispatchDraw(Canvas canvas) { + if (showcaseX < 0 || showcaseY < 0 || shotStateStore.hasShot()) { + super.dispatchDraw(canvas); + return; + } + + //Draw background color + showcaseDrawer.erase(bitmapBuffer); + + // Draw the showcase drawable + if (!hasNoTarget) { + showcaseDrawer.drawShowcase(bitmapBuffer, showcaseX, showcaseY, scaleMultiplier); + showcaseDrawer.drawToCanvas(canvas, bitmapBuffer); + } + + // Draw the text on the screen, recalculating its position if necessary + textDrawer.draw(canvas); + + super.dispatchDraw(canvas); + + } + + @Override + public void onClick(View view) { + hide(); + } + + public void hide() { + clearBitmap(); + // If the type is set to one-shot, store that it has shot + shotStateStore.storeShot(); + mEventListener.onShowcaseViewHide(this); + fadeOutShowcase(); + } + + private void clearBitmap() { + if (bitmapBuffer != null && !bitmapBuffer.isRecycled()) { + bitmapBuffer.recycle(); + bitmapBuffer = null; + } + } + + private void fadeOutShowcase() { + animationFactory.fadeOutView(this, fadeOutMillis, new AnimationEndListener() { + @Override + public void onAnimationEnd() { + setVisibility(View.GONE); + mEventListener.onShowcaseViewDidHide(ShowcaseView.this); + } + }); + } + + public void show() { + mEventListener.onShowcaseViewShow(this); + fadeInShowcase(); + } + + private void fadeInShowcase() { + animationFactory.fadeInView(this, fadeInMillis, + new AnimationStartListener() { + @Override + public void onAnimationStart() { + setVisibility(View.VISIBLE); + } + } + ); + } + + @Override + public boolean onTouch(View view, MotionEvent motionEvent) { + + float xDelta = Math.abs(motionEvent.getRawX() - showcaseX); + float yDelta = Math.abs(motionEvent.getRawY() - showcaseY); + double distanceFromFocus = Math.sqrt(Math.pow(xDelta, 2) + Math.pow(yDelta, 2)); + + if (MotionEvent.ACTION_UP == motionEvent.getAction() && + hideOnTouch && distanceFromFocus > showcaseDrawer.getBlockedRadius()) { + this.hide(); + return true; + } + + return blockTouches && distanceFromFocus > showcaseDrawer.getBlockedRadius(); + } + + private static void insertShowcaseView(ShowcaseView showcaseView, Activity activity) { + ((ViewGroup) activity.getWindow().getDecorView()).addView(showcaseView); + if (!showcaseView.hasShot()) { + showcaseView.show(); + } else { + showcaseView.hideImmediate(); + } + } + + private void hideImmediate() { + setVisibility(GONE); + } + + public void setContentTitle(CharSequence title) { + textDrawer.setContentTitle(title); + } + + public void setContentText(CharSequence text) { + textDrawer.setContentText(text); + } + + private void setScaleMultiplier(float scaleMultiplier) { + this.scaleMultiplier = scaleMultiplier; + } + + @Override + public void onGlobalLayout() { + if (!shotStateStore.hasShot()) { + updateBitmap(); + } + } + + public void hideButton() { + mEndButton.setVisibility(GONE); + } + + public void showButton() { + mEndButton.setVisibility(VISIBLE); + } + + /** + * Builder class which allows easier creation of {@link ShowcaseView}s. + * It is recommended that you use this Builder class. + */ + public static class Builder { + + final ShowcaseView showcaseView; + private final Activity activity; + + public Builder(Activity activity) { + this(activity, false); + } + + public Builder(Activity activity, boolean useNewStyle) { + this.activity = activity; + this.showcaseView = new ShowcaseView(activity, useNewStyle); + this.showcaseView.setTarget(Target.NONE); + } + + /** + * Create the {@link com.github.amlcurran.showcaseview.ShowcaseView} and show it. + * + * @return the created ShowcaseView + */ + public ShowcaseView build() { + insertShowcaseView(showcaseView, activity); + return showcaseView; + } + + /** + * Set the title text shown on the ShowcaseView. + */ + public Builder setContentTitle(int resId) { + return setContentTitle(activity.getString(resId)); + } + + /** + * Set the title text shown on the ShowcaseView. + */ + public Builder setContentTitle(CharSequence title) { + showcaseView.setContentTitle(title); + return this; + } + + /** + * Set the descriptive text shown on the ShowcaseView. + */ + public Builder setContentText(int resId) { + return setContentText(activity.getString(resId)); + } + + /** + * Set the descriptive text shown on the ShowcaseView. + */ + public Builder setContentText(CharSequence text) { + showcaseView.setContentText(text); + return this; + } + + /** + * Set the target of the showcase. + * + * @param target a {@link com.github.amlcurran.showcaseview.targets.Target} representing + * the item to showcase (e.g., a button, or action item). + */ + public Builder setTarget(Target target) { + showcaseView.setTarget(target); + return this; + } + + /** + * Set the style of the ShowcaseView. See the sample app for example styles. + */ + public Builder setStyle(int theme) { + showcaseView.setStyle(theme); + return this; + } + + /** + * Set a listener which will override the button clicks. + * <p/> + * Note that you will have to manually hide the ShowcaseView + */ + public Builder setOnClickListener(OnClickListener onClickListener) { + showcaseView.overrideButtonClick(onClickListener); + return this; + } + + /** + * Don't make the ShowcaseView block touches on itself. This doesn't + * block touches in the showcased area. + * <p/> + * By default, the ShowcaseView does block touches + */ + public Builder doNotBlockTouches() { + showcaseView.setBlocksTouches(false); + return this; + } + + /** + * Make this ShowcaseView hide when the user touches outside the showcased area. + * This enables {@link #doNotBlockTouches()} as well. + * <p/> + * By default, the ShowcaseView doesn't hide on touch. + */ + public Builder hideOnTouchOutside() { + showcaseView.setBlocksTouches(true); + showcaseView.setHideOnTouchOutside(true); + return this; + } + + /** + * Set the ShowcaseView to only ever show once. + * + * @param shotId a unique identifier (<em>across the app</em>) to store + * whether this ShowcaseView has been shown. + */ + public Builder singleShot(long shotId) { + showcaseView.setSingleShot(shotId); + return this; + } + + public Builder setShowcaseEventListener(OnShowcaseEventListener showcaseEventListener) { + showcaseView.setOnShowcaseEventListener(showcaseEventListener); + return this; + } + } + + /** + * Set whether the text should be centred in the screen, or left-aligned (which is the default). + */ + public void setShouldCentreText(boolean shouldCentreText) { + this.shouldCentreText = shouldCentreText; + hasAlteredText = true; + invalidate(); + } + + /** + * @see com.github.amlcurran.showcaseview.ShowcaseView.Builder#setSingleShot(long) + */ + private void setSingleShot(long shotId) { + shotStateStore.setSingleShot(shotId); + } + + /** + * Change the position of the ShowcaseView's button from the default bottom-right position. + * + * @param layoutParams a {@link android.widget.RelativeLayout.LayoutParams} representing + * the new position of the button + */ + public void setButtonPosition(RelativeLayout.LayoutParams layoutParams) { + mEndButton.setLayoutParams(layoutParams); + } + + /** + * Set the duration of the fading in and fading out of the ShowcaseView + */ + private void setFadeDurations(long fadeInMillis, long fadeOutMillis) { + this.fadeInMillis = fadeInMillis; + this.fadeOutMillis = fadeOutMillis; + } + + /** + * @see com.github.amlcurran.showcaseview.ShowcaseView.Builder#hideOnTouchOutside() + */ + public void setHideOnTouchOutside(boolean hideOnTouch) { + this.hideOnTouch = hideOnTouch; + } + + /** + * @see com.github.amlcurran.showcaseview.ShowcaseView.Builder#doNotBlockTouches() + */ + public void setBlocksTouches(boolean blockTouches) { + this.blockTouches = blockTouches; + } + + /** + * @see com.github.amlcurran.showcaseview.ShowcaseView.Builder#setStyle(int) + */ + public void setStyle(int theme) { + TypedArray array = getContext().obtainStyledAttributes(theme, R.styleable.ShowcaseView); + updateStyle(array, true); + } + + private void updateStyle(TypedArray styled, boolean invalidate) { + int backgroundColor = styled.getColor(R.styleable.ShowcaseView_sv_backgroundColor, Color.argb(128, 80, 80, 80)); + int showcaseColor = styled.getColor(R.styleable.ShowcaseView_sv_showcaseColor, HOLO_BLUE); + String buttonText = styled.getString(R.styleable.ShowcaseView_sv_buttonText); + if (TextUtils.isEmpty(buttonText)) { + buttonText = getResources().getString(android.R.string.ok); + } + boolean tintButton = styled.getBoolean(R.styleable.ShowcaseView_sv_tintButtonColor, true); + + int titleTextAppearance = styled.getResourceId(R.styleable.ShowcaseView_sv_titleTextAppearance, + R.style.TextAppearance_ShowcaseView_Title); + int detailTextAppearance = styled.getResourceId(R.styleable.ShowcaseView_sv_detailTextAppearance, + R.style.TextAppearance_ShowcaseView_Detail); + + styled.recycle(); + + showcaseDrawer.setShowcaseColour(showcaseColor); + showcaseDrawer.setBackgroundColour(backgroundColor); + tintButton(showcaseColor, tintButton); + mEndButton.setText(buttonText); + textDrawer.setTitleStyling(titleTextAppearance); + textDrawer.setDetailStyling(detailTextAppearance); + hasAlteredText = true; + + if (invalidate) { + invalidate(); + } + } + + private void tintButton(int showcaseColor, boolean tintButton) { + if (tintButton) { + mEndButton.getBackground().setColorFilter(showcaseColor, PorterDuff.Mode.MULTIPLY); + } else { + mEndButton.getBackground().setColorFilter(HOLO_BLUE, PorterDuff.Mode.MULTIPLY); + } + } + +} diff --git a/showcaseview/java/com/github/amlcurran/showcaseview/StandardShowcaseDrawer.java b/showcaseview/java/com/github/amlcurran/showcaseview/StandardShowcaseDrawer.java new file mode 100644 index 0000000..843b75b --- /dev/null +++ b/showcaseview/java/com/github/amlcurran/showcaseview/StandardShowcaseDrawer.java @@ -0,0 +1,99 @@ +/* + * Copyright 2014 Alex Curran + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.github.amlcurran.showcaseview; + +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; +import android.graphics.drawable.Drawable; + +/** + * Created by curraa01 on 13/10/2013. + */ +class StandardShowcaseDrawer implements ShowcaseDrawer { + + protected final Paint eraserPaint; + protected final Drawable showcaseDrawable; + private final Paint basicPaint; + private final float showcaseRadius; + protected int backgroundColour; + + public StandardShowcaseDrawer(Resources resources) { + PorterDuffXfermode xfermode = new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY); + eraserPaint = new Paint(); + eraserPaint.setColor(0xFFFFFF); + eraserPaint.setAlpha(0); + eraserPaint.setXfermode(xfermode); + eraserPaint.setAntiAlias(true); + basicPaint = new Paint(); + showcaseRadius = resources.getDimension(R.dimen.showcase_radius); + showcaseDrawable = resources.getDrawable(R.drawable.cling_bleached); + } + + @Override + public void setShowcaseColour(int color) { + showcaseDrawable.setColorFilter(color, PorterDuff.Mode.MULTIPLY); + } + + @Override + public void drawShowcase(Bitmap buffer, float x, float y, float scaleMultiplier) { + Canvas bufferCanvas = new Canvas(buffer); + bufferCanvas.drawCircle(x, y, showcaseRadius, eraserPaint); + int halfW = getShowcaseWidth() / 2; + int halfH = getShowcaseHeight() / 2; + int left = (int) (x - halfW); + int top = (int) (y - halfH); + showcaseDrawable.setBounds(left, top, + left + getShowcaseWidth(), + top + getShowcaseHeight()); + showcaseDrawable.draw(bufferCanvas); + } + + @Override + public int getShowcaseWidth() { + return showcaseDrawable.getIntrinsicWidth(); + } + + @Override + public int getShowcaseHeight() { + return showcaseDrawable.getIntrinsicHeight(); + } + + @Override + public float getBlockedRadius() { + return showcaseRadius; + } + + @Override + public void setBackgroundColour(int backgroundColor) { + this.backgroundColour = backgroundColor; + } + + @Override + public void erase(Bitmap bitmapBuffer) { + bitmapBuffer.eraseColor(backgroundColour); + } + + @Override + public void drawToCanvas(Canvas canvas, Bitmap bitmapBuffer) { + canvas.drawBitmap(bitmapBuffer, 0, 0, basicPaint); + } + +} diff --git a/showcaseview/java/com/github/amlcurran/showcaseview/TextDrawer.java b/showcaseview/java/com/github/amlcurran/showcaseview/TextDrawer.java new file mode 100644 index 0000000..3936d7b --- /dev/null +++ b/showcaseview/java/com/github/amlcurran/showcaseview/TextDrawer.java @@ -0,0 +1,217 @@ +/* + * Copyright 2014 Alex Curran + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.github.amlcurran.showcaseview; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.text.DynamicLayout; +import android.text.Layout; +import android.text.SpannableString; +import android.text.TextPaint; +import android.text.TextUtils; +import android.text.style.TextAppearanceSpan; + +/** + * Draws the text as required by the ShowcaseView + */ +class TextDrawer { + + private final TextPaint titlePaint; + private final TextPaint textPaint; + private final Context context; + private final ShowcaseAreaCalculator calculator; + private final float padding; + private final float actionBarOffset; + + private CharSequence mTitle, mDetails; + private float[] mBestTextPosition = new float[3]; + private DynamicLayout mDynamicTitleLayout; + private DynamicLayout mDynamicDetailLayout; + private TextAppearanceSpan mTitleSpan; + private TextAppearanceSpan mDetailSpan; + private boolean hasRecalculated; + + public TextDrawer(Resources resources, ShowcaseAreaCalculator calculator, Context context) { + padding = resources.getDimension(R.dimen.text_padding); + actionBarOffset = resources.getDimension(R.dimen.action_bar_offset); + + this.calculator = calculator; + this.context = context; + + titlePaint = new TextPaint(); + titlePaint.setAntiAlias(true); + + textPaint = new TextPaint(); + textPaint.setAntiAlias(true); + } + + public void draw(Canvas canvas) { + if (shouldDrawText()) { + float[] textPosition = getBestTextPosition(); + + if (!TextUtils.isEmpty(mTitle)) { + canvas.save(); + if (hasRecalculated) { + mDynamicTitleLayout = new DynamicLayout(mTitle, titlePaint, + (int) textPosition[2], Layout.Alignment.ALIGN_NORMAL, + 1.0f, 1.0f, true); + } + if (mDynamicTitleLayout != null) { + canvas.translate(textPosition[0], textPosition[1]); + mDynamicTitleLayout.draw(canvas); + canvas.restore(); + } + } + + if (!TextUtils.isEmpty(mDetails)) { + canvas.save(); + if (hasRecalculated) { + mDynamicDetailLayout = new DynamicLayout(mDetails, textPaint, + (int) textPosition[2], + Layout.Alignment.ALIGN_NORMAL, + 1.2f, 1.0f, true); + } + float offsetForTitle = mDynamicTitleLayout != null ? mDynamicTitleLayout.getHeight() : + 0; + if (mDynamicDetailLayout != null) { + canvas.translate(textPosition[0], textPosition[1] + offsetForTitle); + mDynamicDetailLayout.draw(canvas); + canvas.restore(); + } + + } + } + hasRecalculated = false; + } + + public void setContentText(CharSequence details) { + if (details != null) { + SpannableString ssbDetail = new SpannableString(details); + ssbDetail.setSpan(mDetailSpan, 0, ssbDetail.length(), 0); + mDetails = ssbDetail; + } + } + + public void setContentTitle(CharSequence title) { + if (title != null) { + SpannableString ssbTitle = new SpannableString(title); + ssbTitle.setSpan(mTitleSpan, 0, ssbTitle.length(), 0); + mTitle = ssbTitle; + } + } + + /** + * Calculates the best place to position text + * @param canvasW width of the screen + * @param canvasH height of the screen + * @param shouldCentreText + */ + public void calculateTextPosition(int canvasW, int canvasH, ShowcaseView showcaseView, boolean shouldCentreText) { + + Rect showcase = showcaseView.hasShowcaseView() ? + calculator.getShowcaseRect() : + new Rect(); + + int[] areas = new int[4]; //left, top, right, bottom + areas[0] = showcase.left * canvasH; + areas[1] = showcase.top * canvasW; + areas[2] = (canvasW - showcase.right) * canvasH; + areas[3] = (canvasH - showcase.bottom) * canvasW; + + int largest = 0; + for(int i = 1; i < areas.length; i++) { + if(areas[i] > areas[largest]) + largest = i; + } + + // Position text in largest area + switch(largest) { + case 0: + mBestTextPosition[0] = padding; + mBestTextPosition[1] = padding; + mBestTextPosition[2] = showcase.left - 2 * padding; + break; + case 1: + mBestTextPosition[0] = padding; + mBestTextPosition[1] = padding + actionBarOffset; + mBestTextPosition[2] = canvasW - 2 * padding; + break; + case 2: + mBestTextPosition[0] = showcase.right + padding; + mBestTextPosition[1] = padding; + mBestTextPosition[2] = (canvasW - showcase.right) - 2 * padding; + break; + case 3: + mBestTextPosition[0] = padding; + mBestTextPosition[1] = showcase.bottom + padding; + mBestTextPosition[2] = canvasW - 2 * padding; + break; + } + if(shouldCentreText) { + // Center text vertically or horizontally + switch(largest) { + case 0: + case 2: + mBestTextPosition[1] += canvasH / 4; + break; + case 1: + case 3: + mBestTextPosition[2] /= 2; + mBestTextPosition[0] += canvasW / 4; + break; + } + } else { + // As text is not centered add actionbar padding if the text is left or right + switch(largest) { + case 0: + case 2: + mBestTextPosition[1] += actionBarOffset; + break; + } + } + + hasRecalculated = true; + } + + public void setTitleStyling(int styleId) { + mTitleSpan = new TextAppearanceSpan(this.context, styleId); + setContentTitle(mTitle); + } + + public void setDetailStyling(int styleId) { + mDetailSpan = new TextAppearanceSpan(this.context, styleId); + setContentText(mDetails); + } + + public CharSequence getContentTitle() { + return mTitle; + } + + public CharSequence getContentText() { + return mDetails; + } + + public float[] getBestTextPosition() { + return mBestTextPosition; + } + + public boolean shouldDrawText() { + return !TextUtils.isEmpty(mTitle) || !TextUtils.isEmpty(mDetails); + } +} diff --git a/showcaseview/java/com/github/amlcurran/showcaseview/targets/ActionBarReflector.java b/showcaseview/java/com/github/amlcurran/showcaseview/targets/ActionBarReflector.java new file mode 100644 index 0000000..6022b5a --- /dev/null +++ b/showcaseview/java/com/github/amlcurran/showcaseview/targets/ActionBarReflector.java @@ -0,0 +1,54 @@ +/* + * Copyright 2014 Alex Curran + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.github.amlcurran.showcaseview.targets; + +import android.app.Activity; +import android.view.View; +import android.view.ViewParent; + +/** + * Reflector which finds action items in the standard API 11 ActionBar implementation + */ +class ActionBarReflector implements Reflector { + + private Activity mActivity; + + public ActionBarReflector(Activity activity) { + mActivity = activity; + } + + @Override + public ViewParent getActionBarView() { + return getHomeButton().getParent().getParent(); + } + + @Override + public View getHomeButton() { + View homeButton = mActivity.findViewById(android.R.id.home); + if (homeButton == null) { + throw new RuntimeException( + "insertShowcaseViewWithType cannot be used when the theme " + + "has no ActionBar"); + } + return homeButton; + } + + @Override + public void showcaseActionItem(int itemId) { + + } +} diff --git a/showcaseview/java/com/github/amlcurran/showcaseview/targets/ActionBarViewWrapper.java b/showcaseview/java/com/github/amlcurran/showcaseview/targets/ActionBarViewWrapper.java new file mode 100644 index 0000000..dd09d44 --- /dev/null +++ b/showcaseview/java/com/github/amlcurran/showcaseview/targets/ActionBarViewWrapper.java @@ -0,0 +1,164 @@ +/* + * Copyright 2014 Alex Curran + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.github.amlcurran.showcaseview.targets; + +import android.util.Log; +import android.view.View; +import android.view.ViewParent; + +import java.lang.reflect.Field; + +/** + * Class which wraps round the many implementations of ActionBarView and allows finding of Action + * items + */ +class ActionBarViewWrapper { + + private ViewParent mActionBarView; + private Class mActionBarViewClass; + private Class mAbsActionBarViewClass; + + public ActionBarViewWrapper(ViewParent actionBarView) { + if (!actionBarView.getClass().getName().contains("ActionBarView")) { + String previousP = actionBarView.getClass().getName(); + actionBarView = actionBarView.getParent(); + String throwP = actionBarView.getClass().getName(); + if (!actionBarView.getClass().getName().contains("ActionBarView")) { + throw new IllegalStateException("Cannot find ActionBarView for " + + "Activity, instead found " + previousP + " and " + throwP); + } + } + mActionBarView = actionBarView; + mActionBarViewClass = actionBarView.getClass(); + mAbsActionBarViewClass = actionBarView.getClass().getSuperclass(); + } + + /** + * Return the view which represents the spinner on the ActionBar, or null if there isn't one + */ + public View getSpinnerView() { + try { + Field spinnerField = mActionBarViewClass.getDeclaredField("mSpinner"); + spinnerField.setAccessible(true); + return (View) spinnerField.get(mActionBarView); + } catch (NoSuchFieldException e) { + Log.e("TAG", "Failed to find actionbar spinner", e); + } catch (IllegalAccessException e) { + Log.e("TAG", "Failed to access actionbar spinner", e); + } + return null; + } + + /** + * Return the view which represents the title on the ActionBar, or null if there isn't one + */ + public View getTitleView() { + try { + Field mTitleViewField = mActionBarViewClass.getDeclaredField("mTitleView"); + mTitleViewField.setAccessible(true); + return (View) mTitleViewField.get(mActionBarView); + } catch (NoSuchFieldException e) { + Log.e("TAG", "Failed to find actionbar title", e); + } catch (IllegalAccessException e) { + Log.e("TAG", "Failed to access actionbar title", e); + } + return null; + } + + /** + * Return the view which represents the overflow action item on the ActionBar, or null if there isn't one + */ + public View getOverflowView() { + try { + Field actionMenuPresenterField = mAbsActionBarViewClass.getDeclaredField("mActionMenuPresenter"); + actionMenuPresenterField.setAccessible(true); + Object actionMenuPresenter = actionMenuPresenterField.get(mActionBarView); + Field overflowButtonField = actionMenuPresenter.getClass().getDeclaredField("mOverflowButton"); + overflowButtonField.setAccessible(true); + return (View) overflowButtonField.get(actionMenuPresenter); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } + return null; + } + + /** + * Return the view which represents the MediaRouterButton action item on the ActionBar, or null + * if there isn't one + */ + public View getMediaRouterButtonView() { + try { + Field actionMenuPresenterField = + mAbsActionBarViewClass.getDeclaredField("mActionMenuPresenter"); + actionMenuPresenterField.setAccessible(true); + Object actionMenuPresenter = actionMenuPresenterField.get(mActionBarView); + Field mediaRouteButtonField = actionMenuPresenter.getClass() + .getDeclaredField("mScrapActionButtonView"); + mediaRouteButtonField.setAccessible(true); + return (View) mediaRouteButtonField.get(actionMenuPresenter); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } + return null; + } + + public View getActionItem(int actionItemId) { + try { + Field actionMenuPresenterField = mAbsActionBarViewClass.getDeclaredField("mActionMenuPresenter"); + actionMenuPresenterField.setAccessible(true); + Object actionMenuPresenter = actionMenuPresenterField.get(mActionBarView); + + Field menuViewField = actionMenuPresenter.getClass().getSuperclass().getDeclaredField("mMenuView"); + menuViewField.setAccessible(true); + Object menuView = menuViewField.get(actionMenuPresenter); + + Field mChField; + if (menuView.getClass().toString().contains("com.actionbarsherlock")) { + // There are thousands of superclasses to traverse up + // Have to get superclasses because mChildren is private + mChField = menuView.getClass().getSuperclass().getSuperclass() + .getSuperclass().getSuperclass().getDeclaredField("mChildren"); + } else if (menuView.getClass().toString().contains("android.support.v7")) { + mChField = menuView.getClass().getSuperclass().getSuperclass() + .getSuperclass().getDeclaredField("mChildren"); + } else { + mChField = menuView.getClass().getSuperclass().getSuperclass() + .getDeclaredField("mChildren"); + } + mChField.setAccessible(true); + Object[] mChs = (Object[]) mChField.get(menuView); + for (Object mCh : mChs) { + if (mCh != null) { + View v = (View) mCh; + if (v.getId() == actionItemId) { + return v; + } + } + } + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } + return null; + } + +} diff --git a/showcaseview/java/com/github/amlcurran/showcaseview/targets/ActionItemTarget.java b/showcaseview/java/com/github/amlcurran/showcaseview/targets/ActionItemTarget.java new file mode 100644 index 0000000..9abfab0 --- /dev/null +++ b/showcaseview/java/com/github/amlcurran/showcaseview/targets/ActionItemTarget.java @@ -0,0 +1,53 @@ +/* + * Copyright 2014 Alex Curran + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.github.amlcurran.showcaseview.targets; + +import android.app.Activity; +import android.graphics.Point; +import android.view.ViewParent; + +/** + * Represents an Action item to showcase (e.g., one of the buttons on an ActionBar). + * To showcase specific action views such as the home button, use {@link com.github.amlcurran.showcaseview.targets.ActionItemTarget} + * + * @see com.github.amlcurran.showcaseview.targets.ActionItemTarget + */ +public class ActionItemTarget implements Target { + + private final Activity mActivity; + private final int mItemId; + + ActionBarViewWrapper mActionBarWrapper; + + public ActionItemTarget(Activity activity, int itemId) { + mActivity = activity; + mItemId = itemId; + } + + @Override + public Point getPoint() { + setUp(); + return new ViewTarget(mActionBarWrapper.getActionItem(mItemId)).getPoint(); + } + + protected void setUp() { + Reflector reflector = ReflectorFactory.getReflectorForActivity(mActivity); + ViewParent p = reflector.getActionBarView(); //ActionBarView + mActionBarWrapper = new ActionBarViewWrapper(p); + } + +} diff --git a/showcaseview/java/com/github/amlcurran/showcaseview/targets/ActionViewTarget.java b/showcaseview/java/com/github/amlcurran/showcaseview/targets/ActionViewTarget.java new file mode 100644 index 0000000..80e3ac2 --- /dev/null +++ b/showcaseview/java/com/github/amlcurran/showcaseview/targets/ActionViewTarget.java @@ -0,0 +1,75 @@ +/* + * Copyright 2014 Alex Curran + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.github.amlcurran.showcaseview.targets; + +import android.app.Activity; +import android.graphics.Point; +import android.view.ViewParent; + +public class ActionViewTarget implements Target { + + private final Activity mActivity; + private final Type mType; + + ActionBarViewWrapper mActionBarWrapper; + Reflector mReflector; + + public ActionViewTarget(Activity activity, Type type) { + mActivity = activity; + mType = type; + } + + protected void setUp() { + mReflector = ReflectorFactory.getReflectorForActivity(mActivity); + ViewParent p = mReflector.getActionBarView(); //ActionBarView + mActionBarWrapper = new ActionBarViewWrapper(p); + } + + @Override + public Point getPoint() { + Target internal = null; + setUp(); + switch (mType) { + + case SPINNER: + internal = new ViewTarget(mActionBarWrapper.getSpinnerView()); + break; + + case HOME: + internal = new ViewTarget(mReflector.getHomeButton()); + break; + + case OVERFLOW: + internal = new ViewTarget(mActionBarWrapper.getOverflowView()); + break; + + case TITLE: + internal = new ViewTarget(mActionBarWrapper.getTitleView()); + break; + + case MEDIA_ROUTE_BUTTON: + internal = new ViewTarget(mActionBarWrapper.getMediaRouterButtonView()); + break; + + } + return internal.getPoint(); + } + + public enum Type { + SPINNER, HOME, TITLE, OVERFLOW, MEDIA_ROUTE_BUTTON + } +} diff --git a/showcaseview/java/com/github/amlcurran/showcaseview/targets/AppCompatReflector.java b/showcaseview/java/com/github/amlcurran/showcaseview/targets/AppCompatReflector.java new file mode 100644 index 0000000..7f815c8 --- /dev/null +++ b/showcaseview/java/com/github/amlcurran/showcaseview/targets/AppCompatReflector.java @@ -0,0 +1,59 @@ +/* + * Copyright 2014 Alex Curran + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.github.amlcurran.showcaseview.targets; + +import android.app.Activity; +import android.view.View; +import android.view.ViewParent; + +/** + * Created by Alex on 27/10/13. + */ +class AppCompatReflector implements Reflector { + + private Activity mActivity; + + public AppCompatReflector(Activity activity) { + mActivity = activity; + } + + @Override + public ViewParent getActionBarView() { + return getHomeButton().getParent().getParent(); + } + + @Override + public View getHomeButton() { + View homeButton = mActivity.findViewById(android.R.id.home); + if (homeButton != null) { + return homeButton; + } + int homeId = mActivity.getResources().getIdentifier("home", "id", mActivity.getPackageName()); + homeButton = mActivity.findViewById(homeId); + if (homeButton == null) { + throw new RuntimeException( + "insertShowcaseViewWithType cannot be used when the theme " + + "has no ActionBar"); + } + return homeButton; + } + + @Override + public void showcaseActionItem(int itemId) { + + } +} diff --git a/showcaseview/java/com/github/amlcurran/showcaseview/targets/PointTarget.java b/showcaseview/java/com/github/amlcurran/showcaseview/targets/PointTarget.java new file mode 100644 index 0000000..bfdaecd --- /dev/null +++ b/showcaseview/java/com/github/amlcurran/showcaseview/targets/PointTarget.java @@ -0,0 +1,40 @@ +/* + * Copyright 2014 Alex Curran + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.github.amlcurran.showcaseview.targets; + +import android.graphics.Point; + +/** + * Showcase a specific x/y co-ordinate on the screen. + */ +public class PointTarget implements Target { + + private final Point mPoint; + + public PointTarget(Point point) { + mPoint = point; + } + + public PointTarget(int xValue, int yValue) { + mPoint = new Point(xValue, yValue); + } + + @Override + public Point getPoint() { + return mPoint; + } +} diff --git a/showcaseview/java/com/github/amlcurran/showcaseview/targets/Reflector.java b/showcaseview/java/com/github/amlcurran/showcaseview/targets/Reflector.java new file mode 100644 index 0000000..afc3868 --- /dev/null +++ b/showcaseview/java/com/github/amlcurran/showcaseview/targets/Reflector.java @@ -0,0 +1,32 @@ +/* + * Copyright 2014 Alex Curran + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.github.amlcurran.showcaseview.targets; + +import android.view.View; +import android.view.ViewParent; + +interface Reflector { + View getHomeButton(); + + void showcaseActionItem(int itemId); + + ViewParent getActionBarView(); + + public enum ActionBarType { + STANDARD, APP_COMPAT, ACTIONBAR_SHERLOCK + } +} diff --git a/showcaseview/java/com/github/amlcurran/showcaseview/targets/ReflectorFactory.java b/showcaseview/java/com/github/amlcurran/showcaseview/targets/ReflectorFactory.java new file mode 100644 index 0000000..833049e --- /dev/null +++ b/showcaseview/java/com/github/amlcurran/showcaseview/targets/ReflectorFactory.java @@ -0,0 +1,52 @@ +/* + * Copyright 2014 Alex Curran + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.github.amlcurran.showcaseview.targets; + +import android.app.Activity; + +/** + * Base class which uses reflection to determine how to showcase Action Items and Action Views. + */ +class ReflectorFactory { + + public static Reflector getReflectorForActivity(Activity activity) { + switch (searchForActivitySuperClass(activity)) { + case STANDARD: + return new ActionBarReflector(activity); + case APP_COMPAT: + return new AppCompatReflector(activity); + case ACTIONBAR_SHERLOCK: + return new SherlockReflector(activity); + } + return null; + } + + private static Reflector.ActionBarType searchForActivitySuperClass(Activity activity) { + Class currentLevel = activity.getClass(); + while (currentLevel != Activity.class) { + if (currentLevel.getSimpleName().equals("SherlockActivity") || currentLevel.getSimpleName().equals("SherlockFragmentActivity")) { + return Reflector.ActionBarType.ACTIONBAR_SHERLOCK; + } + if (currentLevel.getSimpleName().equals("ActionBarActivity")) { + return Reflector.ActionBarType.APP_COMPAT; + } + currentLevel = currentLevel.getSuperclass(); + } + return Reflector.ActionBarType.STANDARD; + } + +} diff --git a/showcaseview/java/com/github/amlcurran/showcaseview/targets/SherlockReflector.java b/showcaseview/java/com/github/amlcurran/showcaseview/targets/SherlockReflector.java new file mode 100644 index 0000000..ce6f290 --- /dev/null +++ b/showcaseview/java/com/github/amlcurran/showcaseview/targets/SherlockReflector.java @@ -0,0 +1,59 @@ +/* + * Copyright 2014 Alex Curran + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.github.amlcurran.showcaseview.targets; + +import android.app.Activity; +import android.view.View; +import android.view.ViewParent; + +/** + * Created by Alex on 27/10/13. + */ +class SherlockReflector implements Reflector { + + private Activity mActivity; + + public SherlockReflector(Activity activity) { + mActivity = activity; + } + + @Override + public ViewParent getActionBarView() { + return getHomeButton().getParent().getParent(); + } + + @Override + public View getHomeButton() { + View homeButton = mActivity.findViewById(android.R.id.home); + if (homeButton != null) { + return homeButton; + } + int homeId = mActivity.getResources().getIdentifier("abs__home", "id", mActivity.getPackageName()); + homeButton = mActivity.findViewById(homeId); + if (homeButton == null) { + throw new RuntimeException( + "insertShowcaseViewWithType cannot be used when the theme " + + "has no ActionBar"); + } + return homeButton; + } + + @Override + public void showcaseActionItem(int itemId) { + + } +} diff --git a/showcaseview/java/com/github/amlcurran/showcaseview/targets/Target.java b/showcaseview/java/com/github/amlcurran/showcaseview/targets/Target.java new file mode 100644 index 0000000..8132a40 --- /dev/null +++ b/showcaseview/java/com/github/amlcurran/showcaseview/targets/Target.java @@ -0,0 +1,30 @@ +/* + * Copyright 2014 Alex Curran + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.github.amlcurran.showcaseview.targets; + +import android.graphics.Point; + +public interface Target { + Target NONE = new Target() { + @Override + public Point getPoint() { + return new Point(1000000, 1000000); + } + }; + + public Point getPoint(); +} diff --git a/showcaseview/java/com/github/amlcurran/showcaseview/targets/ViewTarget.java b/showcaseview/java/com/github/amlcurran/showcaseview/targets/ViewTarget.java new file mode 100644 index 0000000..d59dd0d --- /dev/null +++ b/showcaseview/java/com/github/amlcurran/showcaseview/targets/ViewTarget.java @@ -0,0 +1,46 @@ +/* + * Copyright 2014 Alex Curran + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.github.amlcurran.showcaseview.targets; + +import android.app.Activity; +import android.graphics.Point; +import android.view.View; + +/** + * Target a view on the screen. This will centre the target on the view. + */ +public class ViewTarget implements Target { + + private final View mView; + + public ViewTarget(View view) { + mView = view; + } + + public ViewTarget(int viewId, Activity activity) { + mView = activity.findViewById(viewId); + } + + @Override + public Point getPoint() { + int[] location = new int[2]; + mView.getLocationInWindow(location); + int x = location[0] + mView.getWidth() / 2; + int y = location[1] + mView.getHeight() / 2; + return new Point(x, y); + } +} diff --git a/showcaseview/project.properties b/showcaseview/project.properties new file mode 100644 index 0000000..91d2b02 --- /dev/null +++ b/showcaseview/project.properties @@ -0,0 +1,15 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system edit +# "ant.properties", and override values to adapt the script to your +# project structure. +# +# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): +#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt + +# Project target. +target=android-19 +android.library=true diff --git a/showcaseview/res/drawable-hdpi/btn_cling_normal.9.png b/showcaseview/res/drawable-hdpi/btn_cling_normal.9.png Binary files differnew file mode 100644 index 0000000..aea8beb --- /dev/null +++ b/showcaseview/res/drawable-hdpi/btn_cling_normal.9.png diff --git a/showcaseview/res/drawable-hdpi/btn_cling_pressed.9.png b/showcaseview/res/drawable-hdpi/btn_cling_pressed.9.png Binary files differnew file mode 100644 index 0000000..ebefd20 --- /dev/null +++ b/showcaseview/res/drawable-hdpi/btn_cling_pressed.9.png diff --git a/showcaseview/res/drawable-hdpi/cling.png b/showcaseview/res/drawable-hdpi/cling.png Binary files differnew file mode 100644 index 0000000..d605799 --- /dev/null +++ b/showcaseview/res/drawable-hdpi/cling.png diff --git a/showcaseview/res/drawable-hdpi/hand.png b/showcaseview/res/drawable-hdpi/hand.png Binary files differnew file mode 100644 index 0000000..bd4f6df --- /dev/null +++ b/showcaseview/res/drawable-hdpi/hand.png diff --git a/showcaseview/res/drawable-mdpi/btn_cling_normal.9.png b/showcaseview/res/drawable-mdpi/btn_cling_normal.9.png Binary files differnew file mode 100644 index 0000000..43a407e --- /dev/null +++ b/showcaseview/res/drawable-mdpi/btn_cling_normal.9.png diff --git a/showcaseview/res/drawable-mdpi/btn_cling_pressed.9.png b/showcaseview/res/drawable-mdpi/btn_cling_pressed.9.png Binary files differnew file mode 100644 index 0000000..bf0c8cb --- /dev/null +++ b/showcaseview/res/drawable-mdpi/btn_cling_pressed.9.png diff --git a/showcaseview/res/drawable-mdpi/cling.png b/showcaseview/res/drawable-mdpi/cling.png Binary files differnew file mode 100644 index 0000000..fba3a07 --- /dev/null +++ b/showcaseview/res/drawable-mdpi/cling.png diff --git a/showcaseview/res/drawable-mdpi/hand.png b/showcaseview/res/drawable-mdpi/hand.png Binary files differnew file mode 100644 index 0000000..fe5a035 --- /dev/null +++ b/showcaseview/res/drawable-mdpi/hand.png diff --git a/showcaseview/res/drawable-xhdpi/btn_cling_normal.9.png b/showcaseview/res/drawable-xhdpi/btn_cling_normal.9.png Binary files differnew file mode 100644 index 0000000..35511d6 --- /dev/null +++ b/showcaseview/res/drawable-xhdpi/btn_cling_normal.9.png diff --git a/showcaseview/res/drawable-xhdpi/btn_cling_pressed.9.png b/showcaseview/res/drawable-xhdpi/btn_cling_pressed.9.png Binary files differnew file mode 100644 index 0000000..a38b40f --- /dev/null +++ b/showcaseview/res/drawable-xhdpi/btn_cling_pressed.9.png diff --git a/showcaseview/res/drawable-xhdpi/cling.png b/showcaseview/res/drawable-xhdpi/cling.png Binary files differnew file mode 100644 index 0000000..1cee1de --- /dev/null +++ b/showcaseview/res/drawable-xhdpi/cling.png diff --git a/showcaseview/res/drawable-xhdpi/cling_bleached.png b/showcaseview/res/drawable-xhdpi/cling_bleached.png Binary files differnew file mode 100644 index 0000000..9dfecfd --- /dev/null +++ b/showcaseview/res/drawable-xhdpi/cling_bleached.png diff --git a/showcaseview/res/drawable-xhdpi/hand.png b/showcaseview/res/drawable-xhdpi/hand.png Binary files differnew file mode 100644 index 0000000..35b678c --- /dev/null +++ b/showcaseview/res/drawable-xhdpi/hand.png diff --git a/showcaseview/res/drawable/button.xml b/showcaseview/res/drawable/button.xml new file mode 100644 index 0000000..bbc580f --- /dev/null +++ b/showcaseview/res/drawable/button.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright 2014 Alex Curran + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:drawable="@drawable/button_normal" /> +</selector>
\ No newline at end of file diff --git a/showcaseview/res/drawable/button_normal.xml b/showcaseview/res/drawable/button_normal.xml new file mode 100644 index 0000000..ab33c99 --- /dev/null +++ b/showcaseview/res/drawable/button_normal.xml @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright 2014 Alex Curran + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> + + <item> + <shape android:shape="rectangle"> + <padding android:bottom="2dp" /> + <solid android:color="#99FFFFFF" /> + </shape> + </item> + + <!--<item>--> + <!--<shape android:shape="rectangle">--> + <!--<padding android:top="2dp" />--> + <!--<solid android:color="#33FFFFFF" />--> + <!--</shape>--> + <!--</item>--> + + <item> + <shape android:shape="rectangle"> + <solid android:color="#FFFFFF" /> + </shape> + </item> + +</layer-list>
\ No newline at end of file diff --git a/showcaseview/res/drawable/cling_button_bg.xml b/showcaseview/res/drawable/cling_button_bg.xml new file mode 100644 index 0000000..38751c6 --- /dev/null +++ b/showcaseview/res/drawable/cling_button_bg.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright 2014 Alex Curran + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_pressed="true" android:drawable="@drawable/btn_cling_pressed"/> + <item android:drawable="@drawable/btn_cling_normal"/> +</selector> diff --git a/showcaseview/res/layout/handy.xml b/showcaseview/res/layout/handy.xml new file mode 100644 index 0000000..585964f --- /dev/null +++ b/showcaseview/res/layout/handy.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- + ~ Copyright 2014 Alex Curran + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<ImageView xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:src="@drawable/hand" />
\ No newline at end of file diff --git a/showcaseview/res/layout/showcase_button.xml b/showcaseview/res/layout/showcase_button.xml new file mode 100644 index 0000000..1ce45cd --- /dev/null +++ b/showcaseview/res/layout/showcase_button.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright 2014 Alex Curran + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<Button xmlns:android="http://schemas.android.com/apk/res/android" + style="@style/ShowcaseButton" + android:text="@string/ok" + android:id="@id/showcase_button" />
\ No newline at end of file diff --git a/showcaseview/res/values/attrs.xml b/showcaseview/res/values/attrs.xml new file mode 100644 index 0000000..3eff0a4 --- /dev/null +++ b/showcaseview/res/values/attrs.xml @@ -0,0 +1,35 @@ +<!-- + ~ Copyright 2014 Alex Curran + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources> + + <declare-styleable name="ShowcaseView"> + <attr name="sv_backgroundColor" format="color|reference" /> + <attr name="sv_detailTextColor" format="color|reference" /> + <attr name="sv_titleTextColor" format="color|reference" /> + <attr name="sv_buttonBackgroundColor" format="color|reference" /> + <attr name="sv_buttonForegroundColor" format="color|reference" /> + <attr name="sv_buttonText" format="string|reference" /> + <attr name="sv_detailTextAppearance" format="reference" /> + <attr name="sv_titleTextAppearance" format="reference" /> + <attr name="sv_showcaseColor" format="color|reference" /> + <attr name="sv_tintButtonColor" format="boolean|reference" /> + </declare-styleable> + <declare-styleable name="CustomTheme"> + <attr name="showcaseViewStyle" format="reference" /> + </declare-styleable> + +</resources>
\ No newline at end of file diff --git a/showcaseview/res/values/dimens.xml b/showcaseview/res/values/dimens.xml new file mode 100644 index 0000000..2906b72 --- /dev/null +++ b/showcaseview/res/values/dimens.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright 2014 Alex Curran + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources> + <dimen name="button_margin">12dp</dimen> + <dimen name="text_padding">24dp</dimen> + <dimen name="action_bar_offset">66dp</dimen> + <dimen name="showcase_radius">94dp</dimen> + <dimen name="showcase_radius_inner">96dp</dimen> + <dimen name="showcase_radius_outer">128dp</dimen> +</resources>
\ No newline at end of file diff --git a/showcaseview/res/values/ids.xml b/showcaseview/res/values/ids.xml new file mode 100644 index 0000000..2602369 --- /dev/null +++ b/showcaseview/res/values/ids.xml @@ -0,0 +1,21 @@ +<!-- + ~ Copyright 2014 Alex Curran + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources> + <item type="id" name="showcase_button" /> + <item type="id" name="showcase_title_text" /> + <item type="id" name="showcase_sub_text" /> +</resources>
\ No newline at end of file diff --git a/showcaseview/res/values/strings.xml b/showcaseview/res/values/strings.xml new file mode 100644 index 0000000..1708db8 --- /dev/null +++ b/showcaseview/res/values/strings.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright 2014 Alex Curran + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources> + <string name="ok">OK</string> +</resources> diff --git a/showcaseview/res/values/styles.xml b/showcaseview/res/values/styles.xml new file mode 100644 index 0000000..56ea6c4 --- /dev/null +++ b/showcaseview/res/values/styles.xml @@ -0,0 +1,61 @@ +<!-- + ~ Copyright 2014 Alex Curran + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources> + + <style name="ShowcaseButton"> + <item name="android:layout_width">wrap_content</item> + <item name="android:layout_height">wrap_content</item> + <item name="android:minWidth">96dp</item> + <item name="android:gravity">center</item> + <item name="android:textStyle">bold</item> + <item name="android:textColor">#FFFFFF</item> + <item name="android:background">@drawable/button</item> + </style> + + <style name="ShowcaseView.Light"> + <item name="sv_titleTextAppearance">@style/TextAppearance.ShowcaseView.Title</item> + <item name="sv_detailTextAppearance">@style/TextAppearance.ShowcaseView.Detail.Light</item> + <item name="sv_backgroundColor">#3333B5E5</item> + <item name="sv_buttonText">@string/ok</item> + </style> + + <style name="ShowcaseView"> + <item name="sv_titleTextAppearance">@style/TextAppearance.ShowcaseView.Title</item> + <item name="sv_detailTextAppearance">@style/TextAppearance.ShowcaseView.Detail</item> + <item name="sv_backgroundColor">#3333B5E5</item> + <item name="sv_buttonText">@string/ok</item> + </style> + + <style name="TextAppearance.ShowcaseView.Title" parent="android:style/TextAppearance.Large"> + <item name="android:textColor">#33B5E5</item> + </style> + + <style name="TextAppearance.ShowcaseView.Detail" parent="android:style/TextAppearance"> + <item name="android:textColor">#FFFFFF</item> + </style> + + <style name="TextAppearance.ShowcaseView.Detail.Light"> + <item name="android:textColor">#333333</item> + <item name="android:shadowColor">#FF0000</item> + <item name="android:shadowRadius">0</item> + </style> + + <style name="TextAppearance.ShowcaseView.Title.Light"> + <item name="android:shadowRadius">0</item> + </style> + +</resources>
\ No newline at end of file |
