Compatible Android
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0"?>
|
||||
<globals>
|
||||
<global id="manifestOut" value="${manifestDir}" />
|
||||
<global id="appCompat" type="boolean" value="${(minApiLevel lt 14)?string}" />
|
||||
<!-- e.g. getSupportActionBar vs. getActionBar -->
|
||||
<global id="Support" value="${(minApiLevel lt 14)?string('Support','')}" />
|
||||
<global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
|
||||
<global id="resOut" value="${resDir}" />
|
||||
<global id="menuName" value="${classToResource(activityClass)}" />
|
||||
<global id="relativePackage" value="<#if relativePackage?has_content>${relativePackage}<#else>${packageName}</#if>" />
|
||||
</globals>
|
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0"?>
|
||||
<recipe>
|
||||
|
||||
<#if appCompat><dependency mavenUrl="com.android.support:appcompat-v7:19.+"/></#if>
|
||||
|
||||
<merge from="AndroidManifest.xml.ftl"
|
||||
to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
|
||||
|
||||
<instantiate from="res/menu/main.xml.ftl"
|
||||
to="${escapeXmlAttribute(resOut)}/menu/${menuName}.xml" />
|
||||
|
||||
<merge from="res/values/strings.xml.ftl"
|
||||
to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
|
||||
|
||||
<merge from="res/values/dimens.xml.ftl"
|
||||
to="${escapeXmlAttribute(resOut)}/values/dimens.xml" />
|
||||
<merge from="res/values-w820dp/dimens.xml"
|
||||
to="${escapeXmlAttribute(resOut)}/values-w820dp/dimens.xml" />
|
||||
|
||||
<instantiate from="res/layout/activity_simple.xml.ftl"
|
||||
to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
|
||||
|
||||
<instantiate from="src/app_package/SimpleActivity.java.ftl"
|
||||
to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
|
||||
|
||||
<open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
|
||||
<open file="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
|
||||
</recipe>
|
@@ -0,0 +1,24 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
|
||||
<application>
|
||||
<activity android:name="${relativePackage}.${activityClass}"
|
||||
<#if isNewProject>
|
||||
android:label="@string/app_name"
|
||||
<#else>
|
||||
android:label="@string/title_${activityToLayout(activityClass)}"
|
||||
</#if>
|
||||
<#if buildApi gte 16 && parentActivityClass != "">android:parentActivityName="${parentActivityClass}"</#if>>
|
||||
<#if parentActivityClass != "">
|
||||
<meta-data android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="${parentActivityClass}" />
|
||||
</#if>
|
||||
<#if isLauncher>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</#if>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
@@ -0,0 +1,16 @@
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||
android:paddingTop="@dimen/activity_vertical_margin"
|
||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||
tools:context="${relativePackage}.${activityClass}">
|
||||
|
||||
<TextView
|
||||
android:text="@string/hello_world"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</RelativeLayout>
|
@@ -0,0 +1,9 @@
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"<#if appCompat>
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"</#if>
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:context="${relativePackage}.${activityClass}" >
|
||||
<item android:id="@+id/action_settings"
|
||||
android:title="@string/action_settings"
|
||||
android:orderInCategory="100"
|
||||
${(appCompat)?string('app','android')}:showAsAction="never" />
|
||||
</menu>
|
@@ -0,0 +1,6 @@
|
||||
<resources>
|
||||
<!-- Example customization of dimensions originally defined in res/values/dimens.xml
|
||||
(such as screen margins) for screens with more than 820dp of available width. This
|
||||
would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
|
||||
<dimen name="activity_horizontal_margin">64dp</dimen>
|
||||
</resources>
|
@@ -0,0 +1,5 @@
|
||||
<resources>
|
||||
<!-- Default screen margins, per the Android Design guidelines. -->
|
||||
<dimen name="activity_horizontal_margin">16dp</dimen>
|
||||
<dimen name="activity_vertical_margin">16dp</dimen>
|
||||
</resources>
|
@@ -0,0 +1,9 @@
|
||||
<resources>
|
||||
<#if !isNewProject>
|
||||
<string name="title_${activityToLayout(activityClass)}">${escapeXmlString(activityTitle)}</string>
|
||||
</#if>
|
||||
|
||||
<string name="hello_world">Hello world!</string>
|
||||
<string name="action_settings">Settings</string>
|
||||
|
||||
</resources>
|
@@ -0,0 +1,35 @@
|
||||
package ${packageName};
|
||||
|
||||
import <#if appCompat>android.support.v7.app.ActionBarActivity<#else>android.app.Activity</#if>;
|
||||
import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
<#if applicationPackage??>import ${applicationPackage}.R;</#if>
|
||||
|
||||
public class ${activityClass} extends ${(appCompat)?string('ActionBar','')}Activity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.${layoutName});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
// Inflate the menu; this adds items to the action bar if it is present.
|
||||
getMenuInflater().inflate(R.menu.${menuName}, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
// Handle action bar item clicks here. The action bar will
|
||||
// automatically handle clicks on the Home/Up button, so long
|
||||
// as you specify a parent activity in AndroidManifest.xml.
|
||||
int id = item.getItemId();
|
||||
if (id == R.id.action_settings) {
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
@@ -0,0 +1,71 @@
|
||||
<?xml version="1.0"?>
|
||||
<template
|
||||
format="3"
|
||||
revision="4"
|
||||
name="Blank Activity"
|
||||
minApi="7"
|
||||
minBuildApi="14"
|
||||
description="Creates a new blank activity with an action bar.">
|
||||
|
||||
<category value="Activity" />
|
||||
<formfactor value="Mobile" />
|
||||
|
||||
<parameter
|
||||
id="activityClass"
|
||||
name="Activity Name"
|
||||
type="string"
|
||||
constraints="class|unique|nonempty"
|
||||
suggest="${layoutToActivity(layoutName)}"
|
||||
default="MainActivity"
|
||||
help="The name of the activity class to create" />
|
||||
|
||||
<parameter
|
||||
id="layoutName"
|
||||
name="Layout Name"
|
||||
type="string"
|
||||
constraints="layout|unique|nonempty"
|
||||
suggest="${activityToLayout(activityClass)}"
|
||||
default="activity_main"
|
||||
help="The name of the layout to create for the activity" />
|
||||
|
||||
<parameter
|
||||
id="activityTitle"
|
||||
name="Title"
|
||||
type="string"
|
||||
constraints="nonempty"
|
||||
default="MainActivity"
|
||||
suggest="${activityClass}"
|
||||
help="The name of the activity. For launcher activities, the application title." />
|
||||
|
||||
<parameter
|
||||
id="isLauncher"
|
||||
name="Launcher Activity"
|
||||
type="boolean"
|
||||
default="false"
|
||||
help="If true, this activity will have a CATEGORY_LAUNCHER intent filter, making it visible in the launcher" />
|
||||
|
||||
<parameter
|
||||
id="parentActivityClass"
|
||||
name="Hierarchical Parent"
|
||||
type="string"
|
||||
constraints="activity|exists|empty"
|
||||
default=""
|
||||
help="The hierarchical parent activity, used to provide a default implementation for the 'Up' button" />
|
||||
|
||||
<parameter
|
||||
id="packageName"
|
||||
name="Package name"
|
||||
type="string"
|
||||
constraints="package"
|
||||
default="com.mycompany.myapp" />
|
||||
|
||||
<!-- 128x128 thumbnails relative to template.xml -->
|
||||
<thumbs>
|
||||
<!-- default thumbnail is required -->
|
||||
<thumb>template_blank_activity.png</thumb>
|
||||
</thumbs>
|
||||
|
||||
<globals file="globals.xml.ftl" />
|
||||
<execute file="recipe.xml.ftl" />
|
||||
|
||||
</template>
|
After Width: | Height: | Size: 3.2 KiB |
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0"?>
|
||||
<globals>
|
||||
<global id="manifestOut" value="${manifestDir}" />
|
||||
<global id="appCompat" type="boolean" value="${(minApiLevel lt 14)?string}" />
|
||||
<!-- e.g. getSupportActionBar vs. getActionBar -->
|
||||
<global id="Support" value="${(minApiLevel lt 14)?string('Support','')}" />
|
||||
<global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
|
||||
<global id="resOut" value="${resDir}" />
|
||||
<global id="menuName" value="${classToResource(activityClass)}" />
|
||||
<global id="relativePackage" value="<#if relativePackage?has_content>${relativePackage}<#else>${packageName}</#if>" />
|
||||
</globals>
|
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0"?>
|
||||
<recipe>
|
||||
|
||||
<#if appCompat><dependency mavenUrl="com.android.support:appcompat-v7:19.+"/></#if>
|
||||
|
||||
<merge from="AndroidManifest.xml.ftl"
|
||||
to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
|
||||
|
||||
<instantiate from="res/menu/main.xml.ftl"
|
||||
to="${escapeXmlAttribute(resOut)}/menu/${menuName}.xml" />
|
||||
|
||||
<merge from="res/values/strings.xml.ftl"
|
||||
to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
|
||||
|
||||
<merge from="res/values/dimens.xml.ftl"
|
||||
to="${escapeXmlAttribute(resOut)}/values/dimens.xml" />
|
||||
<merge from="res/values-w820dp/dimens.xml"
|
||||
to="${escapeXmlAttribute(resOut)}/values-w820dp/dimens.xml" />
|
||||
|
||||
<instantiate from="res/layout/activity_fragment_container.xml.ftl"
|
||||
to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
|
||||
|
||||
<instantiate from="res/layout/fragment_simple.xml.ftl"
|
||||
to="${escapeXmlAttribute(resOut)}/layout/${fragmentLayoutName}.xml" />
|
||||
|
||||
<instantiate from="src/app_package/SimpleActivity.java.ftl"
|
||||
to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
|
||||
|
||||
<open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
|
||||
<open file="${escapeXmlAttribute(resOut)}/layout/${fragmentLayoutName}.xml" />
|
||||
</recipe>
|
@@ -0,0 +1,24 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
|
||||
<application>
|
||||
<activity android:name="${relativePackage}.${activityClass}"
|
||||
<#if isNewProject>
|
||||
android:label="@string/app_name"
|
||||
<#else>
|
||||
android:label="@string/title_${activityToLayout(activityClass)}"
|
||||
</#if>
|
||||
<#if buildApi gte 16 && parentActivityClass != "">android:parentActivityName="${parentActivityClass}"</#if>>
|
||||
<#if parentActivityClass != "">
|
||||
<meta-data android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="${parentActivityClass}" />
|
||||
</#if>
|
||||
<#if isLauncher>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</#if>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
@@ -0,0 +1,7 @@
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context="${relativePackage}.${activityClass}"
|
||||
tools:ignore="MergeRootFrame" />
|
@@ -0,0 +1,16 @@
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||
android:paddingTop="@dimen/activity_vertical_margin"
|
||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||
tools:context="${relativePackage}.${activityClass}$PlaceholderFragment">
|
||||
|
||||
<TextView
|
||||
android:text="@string/hello_world"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</RelativeLayout>
|
@@ -0,0 +1,9 @@
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"<#if appCompat>
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"</#if>
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:context="${relativePackage}.${activityClass}" >
|
||||
<item android:id="@+id/action_settings"
|
||||
android:title="@string/action_settings"
|
||||
android:orderInCategory="100"
|
||||
${(appCompat)?string('app','android')}:showAsAction="never" />
|
||||
</menu>
|
@@ -0,0 +1,6 @@
|
||||
<resources>
|
||||
<!-- Example customization of dimensions originally defined in res/values/dimens.xml
|
||||
(such as screen margins) for screens with more than 820dp of available width. This
|
||||
would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
|
||||
<dimen name="activity_horizontal_margin">64dp</dimen>
|
||||
</resources>
|
@@ -0,0 +1,5 @@
|
||||
<resources>
|
||||
<!-- Default screen margins, per the Android Design guidelines. -->
|
||||
<dimen name="activity_horizontal_margin">16dp</dimen>
|
||||
<dimen name="activity_vertical_margin">16dp</dimen>
|
||||
</resources>
|
@@ -0,0 +1,7 @@
|
||||
<resources>
|
||||
<#if !isNewProject>
|
||||
<string name="title_${activityToLayout(activityClass)}">${escapeXmlString(activityTitle)}</string>
|
||||
</#if>
|
||||
<string name="hello_world">Hello world!</string>
|
||||
<string name="action_settings">Settings</string>
|
||||
</resources>
|
@@ -0,0 +1,32 @@
|
||||
package ${packageName};
|
||||
|
||||
import <#if appCompat>android.support.v7.app.ActionBarActivity<#else>android.app.Activity</#if>;
|
||||
import android.<#if appCompat>support.v7.</#if>app.ActionBar;
|
||||
import android.<#if appCompat>support.v4.</#if>app.Fragment;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.os.Build;
|
||||
|
||||
<#if applicationPackage??>import ${applicationPackage}.R;</#if>
|
||||
|
||||
public class ${activityClass} extends ${appCompat?string('ActionBar','')}Activity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.${layoutName});
|
||||
if (savedInstanceState == null) {
|
||||
get${Support}FragmentManager().beginTransaction()
|
||||
.add(R.id.container, new PlaceholderFragment())
|
||||
.commit();
|
||||
}
|
||||
}
|
||||
|
||||
<#include "include_options_menu.java.ftl">
|
||||
|
||||
<#include "include_fragment.java.ftl">
|
||||
}
|
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* A placeholder fragment containing a simple view.
|
||||
*/
|
||||
public static class PlaceholderFragment extends Fragment {
|
||||
|
||||
public PlaceholderFragment() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
View rootView = inflater.inflate(R.layout.${fragmentLayoutName}, container, false);
|
||||
return rootView;
|
||||
}
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
// Inflate the menu; this adds items to the action bar if it is present.
|
||||
getMenuInflater().inflate(R.menu.${menuName}, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
// Handle action bar item clicks here. The action bar will
|
||||
// automatically handle clicks on the Home/Up button, so long
|
||||
// as you specify a parent activity in AndroidManifest.xml.
|
||||
int id = item.getItemId();
|
||||
if (id == R.id.action_settings) {
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
@@ -0,0 +1,80 @@
|
||||
<?xml version="1.0"?>
|
||||
<template
|
||||
format="3"
|
||||
revision="4"
|
||||
name="Blank Activity with Fragment"
|
||||
minApi="7"
|
||||
minBuildApi="14"
|
||||
description="Creates a new blank activity, with an action bar and a contained Fragment.">
|
||||
|
||||
<category value="Activity" />
|
||||
<formfactor value="Mobile" />
|
||||
|
||||
<parameter
|
||||
id="activityClass"
|
||||
name="Activity Name"
|
||||
type="string"
|
||||
constraints="class|unique|nonempty"
|
||||
suggest="${layoutToActivity(layoutName)}"
|
||||
default="MainActivity"
|
||||
help="The name of the activity class to create" />
|
||||
|
||||
<parameter
|
||||
id="layoutName"
|
||||
name="Layout Name"
|
||||
type="string"
|
||||
constraints="layout|unique|nonempty"
|
||||
suggest="${activityToLayout(activityClass)}"
|
||||
default="activity_main"
|
||||
help="The name of the layout to create for the activity" />
|
||||
|
||||
<parameter
|
||||
id="fragmentLayoutName"
|
||||
name="Fragment Layout Name"
|
||||
type="string"
|
||||
constraints="layout|unique|nonempty"
|
||||
suggest="fragment_${classToResource(activityClass)}"
|
||||
default="fragment_main"
|
||||
help="The name of the layout to create for the activity's content fragment"/>
|
||||
|
||||
<parameter
|
||||
id="activityTitle"
|
||||
name="Title"
|
||||
type="string"
|
||||
constraints="nonempty"
|
||||
default="MainActivity"
|
||||
suggest="${activityClass}"
|
||||
help="The name of the activity. For launcher activities, the application title." />
|
||||
|
||||
<parameter
|
||||
id="isLauncher"
|
||||
name="Launcher Activity"
|
||||
type="boolean"
|
||||
default="false"
|
||||
help="If true, this activity will have a CATEGORY_LAUNCHER intent filter, making it visible in the launcher" />
|
||||
|
||||
<parameter
|
||||
id="parentActivityClass"
|
||||
name="Hierarchical Parent"
|
||||
type="string"
|
||||
constraints="activity|exists|empty"
|
||||
default=""
|
||||
help="The hierarchical parent activity, used to provide a default implementation for the 'Up' button" />
|
||||
|
||||
<parameter
|
||||
id="packageName"
|
||||
name="Package name"
|
||||
type="string"
|
||||
constraints="package"
|
||||
default="com.mycompany.myapp" />
|
||||
|
||||
<!-- 128x128 thumbnails relative to template.xml -->
|
||||
<thumbs>
|
||||
<!-- default thumbnail is required -->
|
||||
<thumb>template_blank_activity_fragment.png</thumb>
|
||||
</thumbs>
|
||||
|
||||
<globals file="globals.xml.ftl" />
|
||||
<execute file="recipe.xml.ftl" />
|
||||
|
||||
</template>
|
After Width: | Height: | Size: 4.9 KiB |
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0"?>
|
||||
<globals>
|
||||
<global id="manifestOut" value="${manifestDir}" />
|
||||
<global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
|
||||
<global id="resOut" value="${resDir}" />
|
||||
<global id="relativePackage" value="<#if relativePackage?has_content>${relativePackage}<#else>${packageName}</#if>" />
|
||||
</globals>
|
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0"?>
|
||||
<recipe>
|
||||
<merge from="AndroidManifest.xml.ftl"
|
||||
to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
|
||||
|
||||
<merge from="res/values/strings.xml.ftl"
|
||||
to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
|
||||
|
||||
<copy from="res/layout/activity_simple.xml"
|
||||
to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
|
||||
|
||||
<instantiate from="src/app_package/SimpleActivity.java.ftl"
|
||||
to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
|
||||
|
||||
<open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
|
||||
<open file="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
|
||||
</recipe>
|
@@ -0,0 +1,20 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
|
||||
<application>
|
||||
<activity android:name="${relativePackage}.${activityClass}"
|
||||
<#if isNewProject>
|
||||
android:label="@string/app_name"
|
||||
<#else>
|
||||
android:label="@string/title_${activityToLayout(activityClass)}"
|
||||
</#if>
|
||||
>
|
||||
<#if isLauncher>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</#if>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
@@ -0,0 +1,12 @@
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context="${relativePackage}.${activityClass}">
|
||||
|
||||
<TextView
|
||||
android:text="@string/hello_world"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</RelativeLayout>
|
@@ -0,0 +1,6 @@
|
||||
<resources>
|
||||
<#if !isNewProject>
|
||||
<string name="title_${activityToLayout(activityClass)}">${escapeXmlString(activityTitle)}</string>
|
||||
</#if>
|
||||
<string name="hello_world">Hello world!</string>
|
||||
</resources>
|
@@ -0,0 +1,15 @@
|
||||
package ${packageName};
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
|
||||
public class ${activityClass} extends Activity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.${layoutName});
|
||||
}
|
||||
}
|
@@ -0,0 +1,63 @@
|
||||
<?xml version="1.0"?>
|
||||
<template
|
||||
format="3"
|
||||
revision="4"
|
||||
name="Empty Activity"
|
||||
minApi="7"
|
||||
minBuildApi="14"
|
||||
description="Creates a new empty activity">
|
||||
|
||||
<category value="Activity" />
|
||||
<formfactor value="Mobile" />
|
||||
|
||||
<parameter
|
||||
id="activityClass"
|
||||
name="Activity Name"
|
||||
type="string"
|
||||
constraints="class|unique|nonempty"
|
||||
suggest="${layoutToActivity(layoutName)}"
|
||||
default="MainActivity"
|
||||
help="The name of the activity class to create" />
|
||||
|
||||
<parameter
|
||||
id="layoutName"
|
||||
name="Layout Name"
|
||||
type="string"
|
||||
constraints="layout|unique|nonempty"
|
||||
suggest="${activityToLayout(activityClass)}"
|
||||
default="activity_main"
|
||||
help="The name of the layout to create for the activity" />
|
||||
|
||||
<parameter
|
||||
id="activityTitle"
|
||||
name="Title"
|
||||
type="string"
|
||||
constraints="nonempty"
|
||||
default="MainActivity"
|
||||
suggest="${activityClass}"
|
||||
help="The name of the activity. For launcher activities, the application title." />
|
||||
|
||||
<parameter
|
||||
id="isLauncher"
|
||||
name="Launcher Activity"
|
||||
type="boolean"
|
||||
default="false"
|
||||
help="If true, this activity will have a CATEGORY_LAUNCHER intent filter, making it visible in the launcher" />
|
||||
|
||||
<parameter
|
||||
id="packageName"
|
||||
name="Package name"
|
||||
type="string"
|
||||
constraints="package"
|
||||
default="com.mycompany.myapp" />
|
||||
|
||||
<!-- 128x128 thumbnails relative to template.xml -->
|
||||
<thumbs>
|
||||
<!-- default thumbnail is required -->
|
||||
<thumb>template_blank_activity.png</thumb>
|
||||
</thumbs>
|
||||
|
||||
<globals file="globals.xml.ftl" />
|
||||
<execute file="recipe.xml.ftl" />
|
||||
|
||||
</template>
|
After Width: | Height: | Size: 3.2 KiB |
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0"?>
|
||||
<globals>
|
||||
<global id="projectOut" value="." />
|
||||
<global id="manifestOut" value="${manifestDir}" />
|
||||
<global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
|
||||
<global id="resOut" value="${resDir}" />
|
||||
<global id="simpleName" value="${activityToLayout(activityClass)}" />
|
||||
<global id="relativePackage" value="<#if relativePackage?has_content>${relativePackage}<#else>${packageName}</#if>" />
|
||||
</globals>
|
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0"?>
|
||||
<recipe>
|
||||
<dependency mavenUrl="com.android.support:support-v4:19.+" />
|
||||
|
||||
<merge from="AndroidManifest.xml.ftl"
|
||||
to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
|
||||
|
||||
<merge from="res/values/attrs.xml"
|
||||
to="${escapeXmlAttribute(resOut)}/values/attrs.xml" />
|
||||
<merge from="res/values/colors.xml"
|
||||
to="${escapeXmlAttribute(resOut)}/values/colors.xml" />
|
||||
<merge from="res/values/styles.xml"
|
||||
to="${escapeXmlAttribute(resOut)}/values/styles.xml" />
|
||||
<merge from="res/values-v11/styles.xml"
|
||||
to="${escapeXmlAttribute(resOut)}/values-v11/styles.xml" />
|
||||
<instantiate from="res/layout/activity_fullscreen.xml.ftl"
|
||||
to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
|
||||
|
||||
<merge from="res/values/strings.xml.ftl"
|
||||
to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
|
||||
|
||||
<instantiate from="src/app_package/FullscreenActivity.java.ftl"
|
||||
to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
|
||||
<instantiate from="src/app_package/util/SystemUiHider.java.ftl"
|
||||
to="${escapeXmlAttribute(srcOut)}/util/SystemUiHider.java" />
|
||||
<instantiate from="src/app_package/util/SystemUiHiderBase.java.ftl"
|
||||
to="${escapeXmlAttribute(srcOut)}/util/SystemUiHiderBase.java" />
|
||||
<instantiate from="src/app_package/util/SystemUiHiderHoneycomb.java.ftl"
|
||||
to="${escapeXmlAttribute(srcOut)}/util/SystemUiHiderHoneycomb.java" />
|
||||
|
||||
<open file="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
|
||||
</recipe>
|
@@ -0,0 +1,26 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
|
||||
<application>
|
||||
<activity android:name="${relativePackage}.${activityClass}"
|
||||
<#if isNewProject>
|
||||
android:label="@string/app_name"
|
||||
<#else>
|
||||
android:label="@string/title_${simpleName}"
|
||||
</#if>
|
||||
android:configChanges="orientation|keyboardHidden|screenSize"
|
||||
android:theme="@style/FullscreenTheme"
|
||||
<#if buildApi gte 16 && parentActivityClass != "">android:parentActivityName="${parentActivityClass}"</#if>>
|
||||
<#if parentActivityClass != "">
|
||||
<meta-data android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="${parentActivityClass}" />
|
||||
</#if>
|
||||
<#if isLauncher>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</#if>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
@@ -0,0 +1,46 @@
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#0099cc"
|
||||
tools:context="${relativePackage}.${activityClass}">
|
||||
|
||||
<!-- The primary full-screen view. This can be replaced with whatever view
|
||||
is needed to present your content, e.g. VideoView, SurfaceView,
|
||||
TextureView, etc. -->
|
||||
<TextView android:id="@+id/fullscreen_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:keepScreenOn="true"
|
||||
android:textColor="#33b5e5"
|
||||
android:textStyle="bold"
|
||||
android:textSize="50sp"
|
||||
android:gravity="center"
|
||||
android:text="@string/dummy_content" />
|
||||
|
||||
<!-- This FrameLayout insets its children based on system windows using
|
||||
android:fitsSystemWindows. -->
|
||||
<FrameLayout android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true">
|
||||
|
||||
<LinearLayout android:id="@+id/fullscreen_content_controls"
|
||||
style="?metaButtonBarStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|center_horizontal"
|
||||
android:background="@color/black_overlay"
|
||||
android:orientation="horizontal"
|
||||
tools:ignore="UselessParent">
|
||||
|
||||
<Button android:id="@+id/dummy_button"
|
||||
style="?metaButtonBarButtonStyle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/dummy_button" />
|
||||
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
|
||||
</FrameLayout>
|
@@ -0,0 +1,15 @@
|
||||
<resources>
|
||||
|
||||
<style name="FullscreenTheme" parent="android:Theme.Holo">
|
||||
<item name="android:actionBarStyle">@style/FullscreenActionBarStyle</item>
|
||||
<item name="android:windowActionBarOverlay">true</item>
|
||||
<item name="android:windowBackground">@null</item>
|
||||
<item name="metaButtonBarStyle">?android:attr/buttonBarStyle</item>
|
||||
<item name="metaButtonBarButtonStyle">?android:attr/buttonBarButtonStyle</item>
|
||||
</style>
|
||||
|
||||
<style name="FullscreenActionBarStyle" parent="android:Widget.Holo.ActionBar">
|
||||
<item name="android:background">@color/black_overlay</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
@@ -0,0 +1,12 @@
|
||||
<resources>
|
||||
|
||||
<!-- Declare custom theme attributes that allow changing which styles are
|
||||
used for button bars depending on the API level.
|
||||
?android:attr/buttonBarStyle is new as of API 11 so this is
|
||||
necessary to support previous API levels. -->
|
||||
<declare-styleable name="ButtonBarContainerTheme">
|
||||
<attr name="metaButtonBarStyle" format="reference" />
|
||||
<attr name="metaButtonBarButtonStyle" format="reference" />
|
||||
</declare-styleable>
|
||||
|
||||
</resources>
|
@@ -0,0 +1,5 @@
|
||||
<resources>
|
||||
|
||||
<color name="black_overlay">#66000000</color>
|
||||
|
||||
</resources>
|
@@ -0,0 +1,9 @@
|
||||
<resources>
|
||||
|
||||
<#if !isNewProject>
|
||||
<string name="title_${simpleName}">${escapeXmlString(activityTitle)}</string>
|
||||
</#if>
|
||||
<string name="dummy_button">Dummy Button</string>
|
||||
<string name="dummy_content">DUMMY\nCONTENT</string>
|
||||
|
||||
</resources>
|
@@ -0,0 +1,22 @@
|
||||
<resources>
|
||||
|
||||
<style name="FullscreenTheme" parent="android:Theme.NoTitleBar">
|
||||
<item name="android:windowContentOverlay">@null</item>
|
||||
<item name="android:windowBackground">@null</item>
|
||||
<item name="metaButtonBarStyle">@style/ButtonBar</item>
|
||||
<item name="metaButtonBarButtonStyle">@style/ButtonBarButton</item>
|
||||
</style>
|
||||
|
||||
<!-- Backward-compatible version of ?android:attr/buttonBarStyle -->
|
||||
<style name="ButtonBar">
|
||||
<item name="android:paddingLeft">2dp</item>
|
||||
<item name="android:paddingTop">5dp</item>
|
||||
<item name="android:paddingRight">2dp</item>
|
||||
<item name="android:paddingBottom">0dp</item>
|
||||
<item name="android:background">@android:drawable/bottom_bar</item>
|
||||
</style>
|
||||
|
||||
<!-- Backward-compatible version of ?android:attr/buttonBarButtonStyle -->
|
||||
<style name="ButtonBarButton" />
|
||||
|
||||
</resources>
|
@@ -0,0 +1,198 @@
|
||||
package ${packageName};
|
||||
|
||||
import ${packageName}.util.SystemUiHider;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
<#if parentActivityClass != "">
|
||||
import android.view.MenuItem;
|
||||
import android.support.v4.app.NavUtils;
|
||||
</#if>
|
||||
<#if applicationPackage??>import ${applicationPackage}.R;</#if>
|
||||
|
||||
/**
|
||||
* An example full-screen activity that shows and hides the system UI (i.e.
|
||||
* status bar and navigation/system bar) with user interaction.
|
||||
*
|
||||
* @see SystemUiHider
|
||||
*/
|
||||
public class ${activityClass} extends Activity {
|
||||
/**
|
||||
* Whether or not the system UI should be auto-hidden after
|
||||
* {@link #AUTO_HIDE_DELAY_MILLIS} milliseconds.
|
||||
*/
|
||||
private static final boolean AUTO_HIDE = true;
|
||||
|
||||
/**
|
||||
* If {@link #AUTO_HIDE} is set, the number of milliseconds to wait after
|
||||
* user interaction before hiding the system UI.
|
||||
*/
|
||||
private static final int AUTO_HIDE_DELAY_MILLIS = 3000;
|
||||
|
||||
/**
|
||||
* If set, will toggle the system UI visibility upon interaction. Otherwise,
|
||||
* will show the system UI visibility upon interaction.
|
||||
*/
|
||||
private static final boolean TOGGLE_ON_CLICK = true;
|
||||
|
||||
/**
|
||||
* The flags to pass to {@link SystemUiHider#getInstance}.
|
||||
*/
|
||||
private static final int HIDER_FLAGS = SystemUiHider.FLAG_HIDE_NAVIGATION;
|
||||
|
||||
/**
|
||||
* The instance of the {@link SystemUiHider} for this activity.
|
||||
*/
|
||||
private SystemUiHider mSystemUiHider;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.${layoutName});
|
||||
<#if parentActivityClass != "">
|
||||
setupActionBar();
|
||||
</#if>
|
||||
|
||||
final View controlsView = findViewById(R.id.fullscreen_content_controls);
|
||||
final View contentView = findViewById(R.id.fullscreen_content);
|
||||
|
||||
// Set up an instance of SystemUiHider to control the system UI for
|
||||
// this activity.
|
||||
mSystemUiHider = SystemUiHider.getInstance(this, contentView, HIDER_FLAGS);
|
||||
mSystemUiHider.setup();
|
||||
mSystemUiHider
|
||||
.setOnVisibilityChangeListener(new SystemUiHider.OnVisibilityChangeListener() {
|
||||
// Cached values.
|
||||
int mControlsHeight;
|
||||
int mShortAnimTime;
|
||||
|
||||
@Override
|
||||
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2)
|
||||
public void onVisibilityChange(boolean visible) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
|
||||
// If the ViewPropertyAnimator API is available
|
||||
// (Honeycomb MR2 and later), use it to animate the
|
||||
// in-layout UI controls at the bottom of the
|
||||
// screen.
|
||||
if (mControlsHeight == 0) {
|
||||
mControlsHeight = controlsView.getHeight();
|
||||
}
|
||||
if (mShortAnimTime == 0) {
|
||||
mShortAnimTime = getResources().getInteger(
|
||||
android.R.integer.config_shortAnimTime);
|
||||
}
|
||||
controlsView.animate()
|
||||
.translationY(visible ? 0 : mControlsHeight)
|
||||
.setDuration(mShortAnimTime);
|
||||
} else {
|
||||
// If the ViewPropertyAnimator APIs aren't
|
||||
// available, simply show or hide the in-layout UI
|
||||
// controls.
|
||||
controlsView.setVisibility(visible ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
if (visible && AUTO_HIDE) {
|
||||
// Schedule a hide().
|
||||
delayedHide(AUTO_HIDE_DELAY_MILLIS);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Set up the user interaction to manually show or hide the system UI.
|
||||
contentView.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
if (TOGGLE_ON_CLICK) {
|
||||
mSystemUiHider.toggle();
|
||||
} else {
|
||||
mSystemUiHider.show();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Upon interacting with UI controls, delay any scheduled hide()
|
||||
// operations to prevent the jarring behavior of controls going away
|
||||
// while interacting with the UI.
|
||||
findViewById(R.id.dummy_button).setOnTouchListener(mDelayHideTouchListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostCreate(Bundle savedInstanceState) {
|
||||
super.onPostCreate(savedInstanceState);
|
||||
|
||||
// Trigger the initial hide() shortly after the activity has been
|
||||
// created, to briefly hint to the user that UI controls
|
||||
// are available.
|
||||
delayedHide(100);
|
||||
}
|
||||
|
||||
<#if parentActivityClass != "">
|
||||
/**
|
||||
* Set up the {@link android.app.ActionBar}, if the API is available.
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
|
||||
private void setupActionBar() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||
// Show the Up button in the action bar.
|
||||
getActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
int id = item.getItemId();
|
||||
if (id == android.R.id.home) {
|
||||
// This ID represents the Home or Up button. In the case of this
|
||||
// activity, the Up button is shown. Use NavUtils to allow users
|
||||
// to navigate up one level in the application structure. For
|
||||
// more details, see the Navigation pattern on Android Design:
|
||||
//
|
||||
// http://developer.android.com/design/patterns/navigation.html#up-vs-back
|
||||
//
|
||||
// TODO: If Settings has multiple levels, Up should navigate up
|
||||
// that hierarchy.
|
||||
NavUtils.navigateUpFromSameTask(this);
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
</#if>
|
||||
|
||||
/**
|
||||
* Touch listener to use for in-layout UI controls to delay hiding the
|
||||
* system UI. This is to prevent the jarring behavior of controls going away
|
||||
* while interacting with activity UI.
|
||||
*/
|
||||
View.OnTouchListener mDelayHideTouchListener = new View.OnTouchListener() {
|
||||
@Override
|
||||
public boolean onTouch(View view, MotionEvent motionEvent) {
|
||||
if (AUTO_HIDE) {
|
||||
delayedHide(AUTO_HIDE_DELAY_MILLIS);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
Handler mHideHandler = new Handler();
|
||||
Runnable mHideRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mSystemUiHider.hide();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Schedules a call to hide() in [delay] milliseconds, canceling any
|
||||
* previously scheduled calls.
|
||||
*/
|
||||
private void delayedHide(int delayMillis) {
|
||||
mHideHandler.removeCallbacks(mHideRunnable);
|
||||
mHideHandler.postDelayed(mHideRunnable, delayMillis);
|
||||
}
|
||||
}
|
@@ -0,0 +1,172 @@
|
||||
package ${packageName}.util;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Build;
|
||||
import android.view.View;
|
||||
|
||||
/**
|
||||
* A utility class that helps with showing and hiding system UI such as the
|
||||
* status bar and navigation/system bar. This class uses backward-compatibility
|
||||
* techniques described in <a href=
|
||||
* "http://developer.android.com/training/backward-compatible-ui/index.html">
|
||||
* Creating Backward-Compatible UIs</a> to ensure that devices running any
|
||||
* version of ndroid OS are supported. More specifically, there are separate
|
||||
* implementations of this abstract class: for newer devices,
|
||||
* {@link #getInstance} will return a {@link SystemUiHiderHoneycomb} instance,
|
||||
* while on older devices {@link #getInstance} will return a
|
||||
* {@link SystemUiHiderBase} instance.
|
||||
* <p>
|
||||
* For more on system bars, see <a href=
|
||||
* "http://developer.android.com/design/get-started/ui-overview.html#system-bars"
|
||||
* > System Bars</a>.
|
||||
*
|
||||
* @see android.view.View#setSystemUiVisibility(int)
|
||||
* @see android.view.WindowManager.LayoutParams#FLAG_FULLSCREEN
|
||||
*/
|
||||
public abstract class SystemUiHider {
|
||||
/**
|
||||
* When this flag is set, the
|
||||
* {@link android.view.WindowManager.LayoutParams#FLAG_LAYOUT_IN_SCREEN}
|
||||
* flag will be set on older devices, making the status bar "float" on top
|
||||
* of the activity layout. This is most useful when there are no controls at
|
||||
* the top of the activity layout.
|
||||
* <p>
|
||||
* This flag isn't used on newer devices because the <a
|
||||
* href="http://developer.android.com/design/patterns/actionbar.html">action
|
||||
* bar</a>, the most important structural element of an Android app, should
|
||||
* be visible and not obscured by the system UI.
|
||||
*/
|
||||
public static final int FLAG_LAYOUT_IN_SCREEN_OLDER_DEVICES = 0x1;
|
||||
|
||||
/**
|
||||
* When this flag is set, {@link #show()} and {@link #hide()} will toggle
|
||||
* the visibility of the status bar. If there is a navigation bar, show and
|
||||
* hide will toggle low profile mode.
|
||||
*/
|
||||
public static final int FLAG_FULLSCREEN = 0x2;
|
||||
|
||||
/**
|
||||
* When this flag is set, {@link #show()} and {@link #hide()} will toggle
|
||||
* the visibility of the navigation bar, if it's present on the device and
|
||||
* the device allows hiding it. In cases where the navigation bar is present
|
||||
* but cannot be hidden, show and hide will toggle low profile mode.
|
||||
*/
|
||||
public static final int FLAG_HIDE_NAVIGATION = FLAG_FULLSCREEN | 0x4;
|
||||
|
||||
/**
|
||||
* The activity associated with this UI hider object.
|
||||
*/
|
||||
protected Activity mActivity;
|
||||
|
||||
/**
|
||||
* The view on which {@link View#setSystemUiVisibility(int)} will be called.
|
||||
*/
|
||||
protected View mAnchorView;
|
||||
|
||||
/**
|
||||
* The current UI hider flags.
|
||||
*
|
||||
* @see #FLAG_FULLSCREEN
|
||||
* @see #FLAG_HIDE_NAVIGATION
|
||||
* @see #FLAG_LAYOUT_IN_SCREEN_OLDER_DEVICES
|
||||
*/
|
||||
protected int mFlags;
|
||||
|
||||
/**
|
||||
* The current visibility callback.
|
||||
*/
|
||||
protected OnVisibilityChangeListener mOnVisibilityChangeListener = sDummyListener;
|
||||
|
||||
/**
|
||||
* Creates and returns an instance of {@link SystemUiHider} that is
|
||||
* appropriate for this device. The object will be either a
|
||||
* {@link SystemUiHiderBase} or {@link SystemUiHiderHoneycomb} depending on
|
||||
* the device.
|
||||
*
|
||||
* @param activity The activity whose window's system UI should be
|
||||
* controlled by this class.
|
||||
* @param anchorView The view on which
|
||||
* {@link View#setSystemUiVisibility(int)} will be called.
|
||||
* @param flags Either 0 or any combination of {@link #FLAG_FULLSCREEN},
|
||||
* {@link #FLAG_HIDE_NAVIGATION}, and
|
||||
* {@link #FLAG_LAYOUT_IN_SCREEN_OLDER_DEVICES}.
|
||||
*/
|
||||
public static SystemUiHider getInstance(Activity activity, View anchorView, int flags) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||
return new SystemUiHiderHoneycomb(activity, anchorView, flags);
|
||||
} else {
|
||||
return new SystemUiHiderBase(activity, anchorView, flags);
|
||||
}
|
||||
}
|
||||
|
||||
protected SystemUiHider(Activity activity, View anchorView, int flags) {
|
||||
mActivity = activity;
|
||||
mAnchorView = anchorView;
|
||||
mFlags = flags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the system UI hider. Should be called from
|
||||
* {@link Activity#onCreate}.
|
||||
*/
|
||||
public abstract void setup();
|
||||
|
||||
/**
|
||||
* Returns whether or not the system UI is visible.
|
||||
*/
|
||||
public abstract boolean isVisible();
|
||||
|
||||
/**
|
||||
* Hide the system UI.
|
||||
*/
|
||||
public abstract void hide();
|
||||
|
||||
/**
|
||||
* Show the system UI.
|
||||
*/
|
||||
public abstract void show();
|
||||
|
||||
/**
|
||||
* Toggle the visibility of the system UI.
|
||||
*/
|
||||
public void toggle() {
|
||||
if (isVisible()) {
|
||||
hide();
|
||||
} else {
|
||||
show();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a callback, to be triggered when the system UI visibility
|
||||
* changes.
|
||||
*/
|
||||
public void setOnVisibilityChangeListener(OnVisibilityChangeListener listener) {
|
||||
if (listener == null) {
|
||||
listener = sDummyListener;
|
||||
}
|
||||
|
||||
mOnVisibilityChangeListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* A dummy no-op callback for use when there is no other listener set.
|
||||
*/
|
||||
private static OnVisibilityChangeListener sDummyListener = new OnVisibilityChangeListener() {
|
||||
@Override
|
||||
public void onVisibilityChange(boolean visible) {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A callback interface used to listen for system UI visibility changes.
|
||||
*/
|
||||
public interface OnVisibilityChangeListener {
|
||||
/**
|
||||
* Called when the system UI visibility has changed.
|
||||
*
|
||||
* @param visible True if the system UI is visible.
|
||||
*/
|
||||
public void onVisibilityChange(boolean visible);
|
||||
}
|
||||
}
|
@@ -0,0 +1,63 @@
|
||||
package ${packageName}.util;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
|
||||
/**
|
||||
* A base implementation of {@link SystemUiHider}. Uses APIs available in all
|
||||
* API levels to show and hide the status bar.
|
||||
*/
|
||||
public class SystemUiHiderBase extends SystemUiHider {
|
||||
/**
|
||||
* Whether or not the system UI is currently visible. This is a cached value
|
||||
* from calls to {@link #hide()} and {@link #show()}.
|
||||
*/
|
||||
private boolean mVisible = true;
|
||||
|
||||
/**
|
||||
* Constructor not intended to be called by clients. Use
|
||||
* {@link SystemUiHider#getInstance} to obtain an instance.
|
||||
*/
|
||||
protected SystemUiHiderBase(Activity activity, View anchorView, int flags) {
|
||||
super(activity, anchorView, flags);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setup() {
|
||||
if ((mFlags & FLAG_LAYOUT_IN_SCREEN_OLDER_DEVICES) == 0) {
|
||||
mActivity.getWindow().setFlags(
|
||||
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
|
||||
| WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
|
||||
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
|
||||
| WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVisible() {
|
||||
return mVisible;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hide() {
|
||||
if ((mFlags & FLAG_FULLSCREEN) != 0) {
|
||||
mActivity.getWindow().setFlags(
|
||||
WindowManager.LayoutParams.FLAG_FULLSCREEN,
|
||||
WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||
}
|
||||
mOnVisibilityChangeListener.onVisibilityChange(false);
|
||||
mVisible = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show() {
|
||||
if ((mFlags & FLAG_FULLSCREEN) != 0) {
|
||||
mActivity.getWindow().setFlags(
|
||||
0,
|
||||
WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||
}
|
||||
mOnVisibilityChangeListener.onVisibilityChange(true);
|
||||
mVisible = true;
|
||||
}
|
||||
}
|
@@ -0,0 +1,133 @@
|
||||
package ${packageName}.util;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.os.Build;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
|
||||
/**
|
||||
* An API 11+ implementation of {@link SystemUiHider}. Uses APIs available in
|
||||
* Honeycomb and later (specifically {@link View#setSystemUiVisibility(int)}) to
|
||||
* show and hide the system UI.
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
|
||||
public class SystemUiHiderHoneycomb extends SystemUiHiderBase {
|
||||
/**
|
||||
* Flags for {@link View#setSystemUiVisibility(int)} to use when showing the
|
||||
* system UI.
|
||||
*/
|
||||
private int mShowFlags;
|
||||
|
||||
/**
|
||||
* Flags for {@link View#setSystemUiVisibility(int)} to use when hiding the
|
||||
* system UI.
|
||||
*/
|
||||
private int mHideFlags;
|
||||
|
||||
/**
|
||||
* Flags to test against the first parameter in
|
||||
* {@link android.view.View.OnSystemUiVisibilityChangeListener#onSystemUiVisibilityChange(int)}
|
||||
* to determine the system UI visibility state.
|
||||
*/
|
||||
private int mTestFlags;
|
||||
|
||||
/**
|
||||
* Whether or not the system UI is currently visible. This is cached from
|
||||
* {@link android.view.View.OnSystemUiVisibilityChangeListener}.
|
||||
*/
|
||||
private boolean mVisible = true;
|
||||
|
||||
/**
|
||||
* Constructor not intended to be called by clients. Use
|
||||
* {@link SystemUiHider#getInstance} to obtain an instance.
|
||||
*/
|
||||
protected SystemUiHiderHoneycomb(Activity activity, View anchorView, int flags) {
|
||||
super(activity, anchorView, flags);
|
||||
|
||||
mShowFlags = View.SYSTEM_UI_FLAG_VISIBLE;
|
||||
mHideFlags = View.SYSTEM_UI_FLAG_LOW_PROFILE;
|
||||
mTestFlags = View.SYSTEM_UI_FLAG_LOW_PROFILE;
|
||||
|
||||
if ((mFlags & FLAG_FULLSCREEN) != 0) {
|
||||
// If the client requested fullscreen, add flags relevant to hiding
|
||||
// the status bar. Note that some of these constants are new as of
|
||||
// API 16 (Jelly Bean). It is safe to use them, as they are inlined
|
||||
// at compile-time and do nothing on pre-Jelly Bean devices.
|
||||
mShowFlags |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
|
||||
mHideFlags |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||
| View.SYSTEM_UI_FLAG_FULLSCREEN;
|
||||
}
|
||||
|
||||
if ((mFlags & FLAG_HIDE_NAVIGATION) != 0) {
|
||||
// If the client requested hiding navigation, add relevant flags.
|
||||
mShowFlags |= View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
|
||||
mHideFlags |= View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
||||
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
|
||||
mTestFlags |= View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
|
||||
}
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public void setup() {
|
||||
mAnchorView.setOnSystemUiVisibilityChangeListener(mSystemUiVisibilityChangeListener);
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public void hide() {
|
||||
mAnchorView.setSystemUiVisibility(mHideFlags);
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public void show() {
|
||||
mAnchorView.setSystemUiVisibility(mShowFlags);
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public boolean isVisible() {
|
||||
return mVisible;
|
||||
}
|
||||
|
||||
private View.OnSystemUiVisibilityChangeListener mSystemUiVisibilityChangeListener
|
||||
= new View.OnSystemUiVisibilityChangeListener() {
|
||||
@Override
|
||||
public void onSystemUiVisibilityChange(int vis) {
|
||||
// Test against mTestFlags to see if the system UI is visible.
|
||||
if ((vis & mTestFlags) != 0) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
|
||||
// Pre-Jelly Bean, we must manually hide the action bar
|
||||
// and use the old window flags API.
|
||||
mActivity.getActionBar().hide();
|
||||
mActivity.getWindow().setFlags(
|
||||
WindowManager.LayoutParams.FLAG_FULLSCREEN,
|
||||
WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||
}
|
||||
|
||||
// Trigger the registered listener and cache the visibility
|
||||
// state.
|
||||
mOnVisibilityChangeListener.onVisibilityChange(false);
|
||||
mVisible = false;
|
||||
|
||||
} else {
|
||||
mAnchorView.setSystemUiVisibility(mShowFlags);
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
|
||||
// Pre-Jelly Bean, we must manually show the action bar
|
||||
// and use the old window flags API.
|
||||
mActivity.getActionBar().show();
|
||||
mActivity.getWindow().setFlags(
|
||||
0,
|
||||
WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||
}
|
||||
|
||||
// Trigger the registered listener and cache the visibility
|
||||
// state.
|
||||
mOnVisibilityChangeListener.onVisibilityChange(true);
|
||||
mVisible = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
@@ -0,0 +1,68 @@
|
||||
<?xml version="1.0"?>
|
||||
<template
|
||||
format="4"
|
||||
revision="4"
|
||||
name="Fullscreen Activity"
|
||||
description="Creates a new activity that toggles the visibility of the system UI (status and navigation bars) and action bar upon user interaction."
|
||||
minApi="4"
|
||||
minBuildApi="16">
|
||||
<dependency name="android-support-v4" revision="8" />
|
||||
<category value="Activity" />
|
||||
<formfactor value="Mobile" />
|
||||
|
||||
<parameter
|
||||
id="activityClass"
|
||||
name="Activity Name"
|
||||
type="string"
|
||||
constraints="class|unique|nonempty"
|
||||
default="FullscreenActivity"
|
||||
help="The name of the activity class to create" />
|
||||
|
||||
<parameter
|
||||
id="layoutName"
|
||||
name="Layout Name"
|
||||
type="string"
|
||||
constraints="layout|unique|nonempty"
|
||||
suggest="${activityToLayout(activityClass)}"
|
||||
default="activity_fullscreen"
|
||||
help="The name of the layout to create for the activity" />
|
||||
|
||||
<parameter
|
||||
id="activityTitle"
|
||||
name="Title"
|
||||
type="string"
|
||||
constraints="nonempty"
|
||||
default="FullscreenActivity"
|
||||
suggest="${activityClass}"
|
||||
help="The name of the activity." />
|
||||
|
||||
<parameter
|
||||
id="isLauncher"
|
||||
name="Launcher Activity"
|
||||
type="boolean"
|
||||
default="false"
|
||||
help="If true, this activity will have a CATEGORY_LAUNCHER intent filter, making it visible in the launcher" />
|
||||
|
||||
<parameter
|
||||
id="parentActivityClass"
|
||||
name="Hierarchical Parent"
|
||||
type="string"
|
||||
constraints="activity|exists|empty"
|
||||
default=""
|
||||
help="The hierarchical parent activity, used to provide a default implementation for the 'Up' button" />
|
||||
|
||||
<parameter
|
||||
id="packageName"
|
||||
name="Package name"
|
||||
type="string"
|
||||
constraints="package"
|
||||
default="com.mycompany.myapp" />
|
||||
|
||||
<thumbs>
|
||||
<thumb>template_fullscreen_activity.png</thumb>
|
||||
</thumbs>
|
||||
|
||||
<globals file="globals.xml.ftl" />
|
||||
<execute file="recipe.xml.ftl" />
|
||||
|
||||
</template>
|
After Width: | Height: | Size: 16 KiB |
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0"?>
|
||||
<globals>
|
||||
<global id="projectOut" value="." />
|
||||
<global id="manifestOut" value="${manifestDir}" />
|
||||
<global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
|
||||
<global id="resOut" value="${resDir}" />
|
||||
<global id="menuName" value="${classToResource(activityClass)}" />
|
||||
<global id="simpleName" value="${activityToLayout(activityClass)}" />
|
||||
<global id="relativePackage" value="<#if relativePackage?has_content>${relativePackage}<#else>${packageName}</#if>" />
|
||||
</globals>
|
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0"?>
|
||||
<recipe>
|
||||
<dependency mavenUrl="com.google.android.gms:play-services:4.2.42" />
|
||||
<dependency mavenUrl="com.android.support:appcompat-v7:19.+" />
|
||||
|
||||
<merge from="AndroidManifest.xml.ftl"
|
||||
to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
|
||||
|
||||
<merge from="res/values/dimens.xml"
|
||||
to="${escapeXmlAttribute(resOut)}/values/dimens.xml" />
|
||||
|
||||
<instantiate from="res/layout/activity_login.xml.ftl"
|
||||
to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
|
||||
|
||||
<instantiate from="res/values/strings.xml.ftl"
|
||||
to="${escapeXmlAttribute(resOut)}/values/strings_${simpleName}.xml" />
|
||||
|
||||
<instantiate from="src/app_package/LoginActivity.java.ftl"
|
||||
to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
|
||||
|
||||
<#if includeGooglePlus>
|
||||
<instantiate from="src/app_package/PlusBaseActivity.java.ftl"
|
||||
to="${escapeXmlAttribute(srcOut)}/PlusBaseActivity.java" />
|
||||
</#if>
|
||||
|
||||
<open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
|
||||
|
||||
</recipe>
|
@@ -0,0 +1,37 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
<#if includeGooglePlus>
|
||||
<!-- To access Google+ APIs: -->
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<!-- To retrieve OAuth 2.0 tokens or invalidate tokens to disconnect a user. This disconnect
|
||||
option is required to comply with the Google+ Sign-In developer policies -->
|
||||
<uses-permission android:name="android.permission.USE_CREDENTIALS" />
|
||||
|
||||
<!-- To retrieve the account name (email) as part of sign-in: -->
|
||||
<uses-permission android:name="android.permission.GET_ACCOUNTS" /></#if>
|
||||
|
||||
<!-- To auto-complete the email text field in the login form with the user's emails --><#if !includeGooglePlus>
|
||||
<uses-permission android:name="android.permission.GET_ACCOUNTS" /></#if>
|
||||
<uses-permission android:name="android.permission.READ_PROFILE" />
|
||||
<uses-permission android:name="android.permission.READ_CONTACTS" />
|
||||
|
||||
<application>
|
||||
<activity android:name=".${activityClass}"
|
||||
<#if isNewProject>
|
||||
android:label="@string/app_name"
|
||||
<#else>
|
||||
android:label="@string/title_${simpleName}"
|
||||
</#if>
|
||||
android:windowSoftInputMode="adjustResize|<#if includeGooglePlus>stateHidden<#else>stateVisible</#if>"
|
||||
<#if buildApi gte 16 && parentActivityClass != "">android:parentActivityName="${parentActivityClass}"</#if>>
|
||||
<#if parentActivityClass != "">
|
||||
<meta-data android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="${parentActivityClass}" />
|
||||
</#if>
|
||||
</activity>
|
||||
<#if includeGooglePlus>
|
||||
<meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version" />
|
||||
</#if>
|
||||
</application>
|
||||
|
||||
</manifest>
|
@@ -0,0 +1,108 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center_horizontal"
|
||||
android:orientation="vertical"
|
||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||
android:paddingTop="@dimen/activity_vertical_margin"
|
||||
tools:context="${relativePackage}.${activityClass}">
|
||||
|
||||
<!-- Login progress -->
|
||||
<ProgressBar
|
||||
android:id="@+id/login_progress"
|
||||
style="?android:attr/progressBarStyleLarge"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<ScrollView
|
||||
android:id="@+id/login_form"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
>
|
||||
<#if includeGooglePlus>
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.gms.common.SignInButton
|
||||
android:id="@+id/plus_sign_in_button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="32dp"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/plus_sign_out_buttons"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
android:weightSum="2">
|
||||
|
||||
<Button
|
||||
android:id="@+id/plus_sign_out_button"
|
||||
style="?android:textAppearanceSmall"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/plus_sign_out"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/plus_disconnect_button"
|
||||
style="?android:textAppearanceSmall"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/plus_disconnect"/>
|
||||
|
||||
</LinearLayout>
|
||||
</#if>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/email_login_form"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<AutoCompleteTextView
|
||||
android:id="@+id/email"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/prompt_email"
|
||||
android:inputType="textEmailAddress"
|
||||
android:maxLines="1"
|
||||
android:singleLine="true"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/password"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/prompt_password"
|
||||
android:imeActionId="@+id/login"
|
||||
android:imeActionLabel="@string/action_sign_in_short"
|
||||
android:imeOptions="actionUnspecified"
|
||||
android:inputType="textPassword"
|
||||
android:maxLines="1"
|
||||
android:singleLine="true"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/email_sign_in_button"
|
||||
style="?android:textAppearanceSmall"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="@string/action_sign_in"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
</LinearLayout>
|
||||
<#if includeGooglePlus>
|
||||
</LinearLayout>
|
||||
</#if>
|
||||
</ScrollView>
|
||||
|
||||
</LinearLayout>
|
||||
|
@@ -0,0 +1,5 @@
|
||||
<resources>
|
||||
<!-- Default screen margins, per the Android Design guidelines. -->
|
||||
<dimen name="activity_horizontal_margin">16dp</dimen>
|
||||
<dimen name="activity_vertical_margin">16dp</dimen>
|
||||
</resources>
|
@@ -0,0 +1,17 @@
|
||||
<resources>
|
||||
<#if !isNewProject>
|
||||
<string name="title_${simpleName}">${escapeXmlString(activityTitle)}</string>
|
||||
</#if>
|
||||
|
||||
<!-- Strings related to login -->
|
||||
<string name="prompt_email">Email</string>
|
||||
<string name="prompt_password">Password (optional)</string>
|
||||
<string name="action_sign_in">Sign in or register</string>
|
||||
<string name="action_sign_in_short">Sign in</string>
|
||||
<#if includeGooglePlus> <string name="plus_sign_out">Switch Google+ account</string>
|
||||
<string name="plus_disconnect">Disconnect from Google+</string></#if>
|
||||
<string name="error_invalid_email">This email address is invalid</string>
|
||||
<string name="error_invalid_password">This password is too short</string>
|
||||
<string name="error_incorrect_password">This password is incorrect</string>
|
||||
<string name="error_field_required">This field is required</string>
|
||||
</resources>
|
@@ -0,0 +1,449 @@
|
||||
package ${packageName};
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.annotation.TargetApi;
|
||||
<#if !includeGooglePlus>import android.app.Activity;</#if>
|
||||
import android.app.LoaderManager.LoaderCallbacks;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.CursorLoader;
|
||||
import android.content.Loader;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
<#if minApiLevel lt 14>import android.os.Build.VERSION;</#if>
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.provider.ContactsContract;
|
||||
import android.text.TextUtils;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.AutoCompleteTextView;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
<#if includeGooglePlus>
|
||||
import com.google.android.gms.common.ConnectionResult;
|
||||
import com.google.android.gms.common.GooglePlayServicesUtil;
|
||||
import com.google.android.gms.common.SignInButton;
|
||||
</#if>
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
<#if applicationPackage??>import ${applicationPackage}.R;</#if>
|
||||
|
||||
/**
|
||||
* A login screen that offers login via email/password<#if includeGooglePlus> and via Google+ sign in</#if>.
|
||||
<#if includeGooglePlus> * <p/>
|
||||
* ************ IMPORTANT SETUP NOTES: ************
|
||||
* In order for Google+ sign in to work with your app, you must first go to:
|
||||
* https://developers.google.com/+/mobile/android/getting-started#step_1_enable_the_google_api
|
||||
* and follow the steps in "Step 1" to create an OAuth 2.0 client for your package.</#if>
|
||||
*/
|
||||
public class ${activityClass} extends <#if includeGooglePlus>PlusBase</#if>Activity implements LoaderCallbacks<Cursor>{
|
||||
|
||||
/**
|
||||
* A dummy authentication store containing known user names and passwords.
|
||||
* TODO: remove after connecting to a real authentication system.
|
||||
*/
|
||||
private static final String[] DUMMY_CREDENTIALS = new String[]{
|
||||
"foo@example.com:hello", "bar@example.com:world"
|
||||
};
|
||||
/**
|
||||
* Keep track of the login task to ensure we can cancel it if requested.
|
||||
*/
|
||||
private UserLoginTask mAuthTask = null;
|
||||
|
||||
// UI references.
|
||||
private AutoCompleteTextView mEmailView;
|
||||
private EditText mPasswordView;
|
||||
private View mProgressView;<#if includeGooglePlus>
|
||||
private View mEmailLoginFormView;
|
||||
private SignInButton mPlusSignInButton;
|
||||
private View mSignOutButtons;</#if>
|
||||
private View mLoginFormView;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_login);
|
||||
<#if parentActivityClass != "">
|
||||
setupActionBar();
|
||||
</#if>
|
||||
<#if includeGooglePlus>
|
||||
|
||||
// Find the Google+ sign in button.
|
||||
mPlusSignInButton = (SignInButton) findViewById(R.id.plus_sign_in_button);
|
||||
if (supportsGooglePlayServices()) {
|
||||
// Set a listener to connect the user when the G+ button is clicked.
|
||||
mPlusSignInButton.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
signIn();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Don't offer G+ sign in if the app's version is too low to support Google Play
|
||||
// Services.
|
||||
mPlusSignInButton.setVisibility(View.GONE);
|
||||
return;
|
||||
}
|
||||
</#if>
|
||||
|
||||
// Set up the login form.
|
||||
mEmailView = (AutoCompleteTextView) findViewById(R.id.email);
|
||||
populateAutoComplete();
|
||||
|
||||
mPasswordView = (EditText) findViewById(R.id.password);
|
||||
mPasswordView.setOnEditorActionListener(new TextView.OnEditorActionListener() {
|
||||
@Override
|
||||
public boolean onEditorAction(TextView textView, int id, KeyEvent keyEvent) {
|
||||
if (id == R.id.login || id == EditorInfo.IME_NULL) {
|
||||
attemptLogin();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
Button mEmailSignInButton = (Button) findViewById(R.id.email_sign_in_button);
|
||||
mEmailSignInButton.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
attemptLogin();
|
||||
}
|
||||
});
|
||||
|
||||
mLoginFormView = findViewById(R.id.login_form);
|
||||
mProgressView = findViewById(R.id.login_progress);<#if includeGooglePlus>
|
||||
mEmailLoginFormView = findViewById(R.id.email_login_form);
|
||||
mSignOutButtons = findViewById(R.id.plus_sign_out_buttons);</#if>
|
||||
}
|
||||
|
||||
private void populateAutoComplete() {
|
||||
<#if minApiLevel gte 14>
|
||||
getLoaderManager().initLoader(0, null, this);
|
||||
<#else>
|
||||
if (VERSION.SDK_INT >= 14) {
|
||||
// Use ContactsContract.Profile (API 14+)
|
||||
getLoaderManager().initLoader(0, null, this);
|
||||
} else if (VERSION.SDK_INT >= 8) {
|
||||
// Use AccountManager (API 8+)
|
||||
new SetupEmailAutoCompleteTask().execute(null, null);
|
||||
}
|
||||
</#if>
|
||||
}
|
||||
|
||||
<#if parentActivityClass != "">
|
||||
/**
|
||||
* Set up the {@link android.app.ActionBar}, if the API is available.
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
|
||||
private void setupActionBar() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||
// Show the Up button in the action bar.
|
||||
getActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
}
|
||||
</#if>
|
||||
|
||||
/**
|
||||
* Attempts to sign in or register the account specified by the login form.
|
||||
* If there are form errors (invalid email, missing fields, etc.), the
|
||||
* errors are presented and no actual login attempt is made.
|
||||
*/
|
||||
public void attemptLogin() {
|
||||
if (mAuthTask != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset errors.
|
||||
mEmailView.setError(null);
|
||||
mPasswordView.setError(null);
|
||||
|
||||
// Store values at the time of the login attempt.
|
||||
String email = mEmailView.getText().toString();
|
||||
String password = mPasswordView.getText().toString();
|
||||
|
||||
boolean cancel = false;
|
||||
View focusView = null;
|
||||
|
||||
// Check for a valid password, if the user entered one.
|
||||
if (!TextUtils.isEmpty(password) && !isPasswordValid(password)) {
|
||||
mPasswordView.setError(getString(R.string.error_invalid_password));
|
||||
focusView = mPasswordView;
|
||||
cancel = true;
|
||||
}
|
||||
|
||||
// Check for a valid email address.
|
||||
if (TextUtils.isEmpty(email)) {
|
||||
mEmailView.setError(getString(R.string.error_field_required));
|
||||
focusView = mEmailView;
|
||||
cancel = true;
|
||||
} else if (!isEmailValid(email)) {
|
||||
mEmailView.setError(getString(R.string.error_invalid_email));
|
||||
focusView = mEmailView;
|
||||
cancel = true;
|
||||
}
|
||||
|
||||
if (cancel) {
|
||||
// There was an error; don't attempt login and focus the first
|
||||
// form field with an error.
|
||||
focusView.requestFocus();
|
||||
} else {
|
||||
// Show a progress spinner, and kick off a background task to
|
||||
// perform the user login attempt.
|
||||
showProgress(true);
|
||||
mAuthTask = new UserLoginTask(email, password);
|
||||
mAuthTask.execute((Void) null);
|
||||
}
|
||||
}
|
||||
private boolean isEmailValid(String email) {
|
||||
//TODO: Replace this with your own logic
|
||||
return email.contains("@");
|
||||
}
|
||||
|
||||
private boolean isPasswordValid(String password) {
|
||||
//TODO: Replace this with your own logic
|
||||
return password.length() > 4;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the progress UI and hides the login form.
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2)
|
||||
public void showProgress(final boolean show) {
|
||||
// On Honeycomb MR2 we have the ViewPropertyAnimator APIs, which allow
|
||||
// for very easy animations. If available, use these APIs to fade-in
|
||||
// the progress spinner.
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
|
||||
int shortAnimTime = getResources().getInteger(android.R.integer.config_shortAnimTime);
|
||||
|
||||
mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
|
||||
mLoginFormView.animate().setDuration(shortAnimTime).alpha(
|
||||
show ? 0 : 1).setListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
});
|
||||
|
||||
mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
|
||||
mProgressView.animate().setDuration(shortAnimTime).alpha(
|
||||
show ? 1 : 0).setListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// The ViewPropertyAnimator APIs are not available, so simply show
|
||||
// and hide the relevant UI components.
|
||||
mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
|
||||
mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
}
|
||||
<#if includeGooglePlus>
|
||||
|
||||
@Override
|
||||
protected void onPlusClientSignIn() {
|
||||
//Set up sign out and disconnect buttons.
|
||||
Button signOutButton = (Button) findViewById(R.id.plus_sign_out_button);
|
||||
signOutButton.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
signOut();
|
||||
}
|
||||
});
|
||||
Button disconnectButton = (Button) findViewById(R.id.plus_disconnect_button);
|
||||
disconnectButton.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
revokeAccess();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPlusClientBlockingUI(boolean show) {
|
||||
showProgress(show);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateConnectButtonState() {
|
||||
//TODO: Update this logic to also handle the user logged in by email.
|
||||
boolean connected = getPlusClient().isConnected();
|
||||
|
||||
mSignOutButtons.setVisibility(connected ? View.VISIBLE : View.GONE);
|
||||
mPlusSignInButton.setVisibility(connected ? View.GONE : View.VISIBLE);
|
||||
mEmailLoginFormView.setVisibility(connected ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPlusClientRevokeAccess() {
|
||||
// TODO: Access to the user's G+ account has been revoked. Per the developer terms, delete
|
||||
// any stored user data here.
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPlusClientSignOut() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the device supports Google Play Services. It's best
|
||||
* practice to check first rather than handling this as an error case.
|
||||
*
|
||||
* @return whether the device supports Google Play Services
|
||||
*/
|
||||
private boolean supportsGooglePlayServices() {
|
||||
return GooglePlayServicesUtil.isGooglePlayServicesAvailable(this) ==
|
||||
ConnectionResult.SUCCESS;
|
||||
}
|
||||
</#if>
|
||||
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
|
||||
return new CursorLoader(this,
|
||||
// Retrieve data rows for the device user's 'profile' contact.
|
||||
Uri.withAppendedPath(ContactsContract.Profile.CONTENT_URI,
|
||||
ContactsContract.Contacts.Data.CONTENT_DIRECTORY), ProfileQuery.PROJECTION,
|
||||
|
||||
// Select only email addresses.
|
||||
ContactsContract.Contacts.Data.MIMETYPE +
|
||||
" = ?", new String[]{ContactsContract.CommonDataKinds.Email
|
||||
.CONTENT_ITEM_TYPE},
|
||||
|
||||
// Show primary email addresses first. Note that there won't be
|
||||
// a primary email address if the user hasn't specified one.
|
||||
ContactsContract.Contacts.Data.IS_PRIMARY + " DESC");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
|
||||
List<String> emails = new ArrayList<String>();
|
||||
cursor.moveToFirst();
|
||||
while (!cursor.isAfterLast()) {
|
||||
emails.add(cursor.getString(ProfileQuery.ADDRESS));
|
||||
cursor.moveToNext();
|
||||
}
|
||||
|
||||
addEmailsToAutoComplete(emails);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<Cursor> cursorLoader) {
|
||||
|
||||
}
|
||||
|
||||
private interface ProfileQuery {
|
||||
String[] PROJECTION = {
|
||||
ContactsContract.CommonDataKinds.Email.ADDRESS,
|
||||
ContactsContract.CommonDataKinds.Email.IS_PRIMARY,
|
||||
};
|
||||
|
||||
int ADDRESS = 0;
|
||||
int IS_PRIMARY = 1;
|
||||
}
|
||||
|
||||
<#if minApiLevel lt 14>
|
||||
/**
|
||||
* Use an AsyncTask to fetch the user's email addresses on a background thread, and update
|
||||
* the email text field with results on the main UI thread.
|
||||
*/
|
||||
class SetupEmailAutoCompleteTask extends AsyncTask<Void, Void, List<String>> {
|
||||
|
||||
@Override
|
||||
protected List<String> doInBackground(Void... voids) {
|
||||
ArrayList<String> emailAddressCollection = new ArrayList<String>();
|
||||
|
||||
// Get all emails from the user's contacts and copy them to a list.
|
||||
ContentResolver cr = getContentResolver();
|
||||
Cursor emailCur = cr.query(ContactsContract.CommonDataKinds.Email.CONTENT_URI, null,
|
||||
null, null, null);
|
||||
while (emailCur.moveToNext()) {
|
||||
String email = emailCur.getString(emailCur.getColumnIndex(ContactsContract
|
||||
.CommonDataKinds.Email.DATA));
|
||||
emailAddressCollection.add(email);
|
||||
}
|
||||
emailCur.close();
|
||||
|
||||
return emailAddressCollection;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(List<String> emailAddressCollection) {
|
||||
addEmailsToAutoComplete(emailAddressCollection);
|
||||
}
|
||||
}
|
||||
</#if>
|
||||
|
||||
private void addEmailsToAutoComplete(List<String> emailAddressCollection) {
|
||||
//Create adapter to tell the AutoCompleteTextView what to show in its dropdown list.
|
||||
ArrayAdapter<String> adapter =
|
||||
new ArrayAdapter<String>(LoginActivity.this,
|
||||
android.R.layout.simple_dropdown_item_1line, emailAddressCollection);
|
||||
|
||||
mEmailView.setAdapter(adapter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an asynchronous login/registration task used to authenticate
|
||||
* the user.
|
||||
*/
|
||||
public class UserLoginTask extends AsyncTask<Void, Void, Boolean> {
|
||||
|
||||
private final String mEmail;
|
||||
private final String mPassword;
|
||||
|
||||
UserLoginTask(String email, String password) {
|
||||
mEmail = email;
|
||||
mPassword = password;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Boolean doInBackground(Void... params) {
|
||||
// TODO: attempt authentication against a network service.
|
||||
|
||||
try {
|
||||
// Simulate network access.
|
||||
Thread.sleep(2000);
|
||||
} catch (InterruptedException e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (String credential : DUMMY_CREDENTIALS) {
|
||||
String[] pieces = credential.split(":");
|
||||
if (pieces[0].equals(mEmail)) {
|
||||
// Account exists, return true if the password matches.
|
||||
return pieces[1].equals(mPassword);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: register the new account here.
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(final Boolean success) {
|
||||
mAuthTask = null;
|
||||
showProgress(false);
|
||||
|
||||
if (success) {
|
||||
finish();
|
||||
} else {
|
||||
mPasswordView.setError(getString(R.string.error_incorrect_password));
|
||||
mPasswordView.requestFocus();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCancelled() {
|
||||
mAuthTask = null;
|
||||
showProgress(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,282 @@
|
||||
package ${packageName};
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.IntentSender;
|
||||
import android.os.Bundle;
|
||||
<#if minApiLevel lt 14>import android.support.v7.app.ActionBarActivity;</#if>
|
||||
<#if minApiLevel gte 14>import android.app.Activity;</#if>
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.android.gms.common.ConnectionResult;
|
||||
import com.google.android.gms.common.GooglePlayServicesClient;
|
||||
import com.google.android.gms.common.Scopes;
|
||||
import com.google.android.gms.plus.PlusClient;
|
||||
<#if applicationPackage??>import ${applicationPackage}.R;</#if>
|
||||
|
||||
/**
|
||||
* A base class to wrap communication with the Google Play Services PlusClient.
|
||||
*/
|
||||
public abstract class PlusBaseActivity extends <#if minApiLevel lt 14>ActionBar</#if>Activity
|
||||
implements GooglePlayServicesClient.ConnectionCallbacks,
|
||||
GooglePlayServicesClient.OnConnectionFailedListener {
|
||||
|
||||
private static final String TAG = PlusBaseActivity.class.getSimpleName();
|
||||
|
||||
// A magic number we will use to know that our sign-in error resolution activity has completed
|
||||
private static final int OUR_REQUEST_CODE = 49404;
|
||||
|
||||
// A flag to stop multiple dialogues appearing for the user
|
||||
private boolean mAutoResolveOnFail;
|
||||
|
||||
// A flag to track when a connection is already in progress
|
||||
public boolean mPlusClientIsConnecting = false;
|
||||
|
||||
// This is the helper object that connects to Google Play Services.
|
||||
private PlusClient mPlusClient;
|
||||
|
||||
// The saved result from {@link #onConnectionFailed(ConnectionResult)}. If a connection
|
||||
// attempt has been made, this is non-null.
|
||||
// If this IS null, then the connect method is still running.
|
||||
private ConnectionResult mConnectionResult;
|
||||
|
||||
/**
|
||||
* Called when the {@link PlusClient} revokes access to this app.
|
||||
*/
|
||||
protected abstract void onPlusClientRevokeAccess();
|
||||
|
||||
/**
|
||||
* Called when the PlusClient is successfully connected.
|
||||
*/
|
||||
protected abstract void onPlusClientSignIn();
|
||||
|
||||
/**
|
||||
* Called when the {@link PlusClient} is disconnected.
|
||||
*/
|
||||
protected abstract void onPlusClientSignOut();
|
||||
|
||||
/**
|
||||
* Called when the {@link PlusClient} is blocking the UI. If you have a progress bar widget,
|
||||
* this tells you when to show or hide it.
|
||||
*/
|
||||
protected abstract void onPlusClientBlockingUI(boolean show);
|
||||
|
||||
/**
|
||||
* Called when there is a change in connection state. If you have "Sign in"/ "Connect",
|
||||
* "Sign out"/ "Disconnect", or "Revoke access" buttons, this lets you know when their states
|
||||
* need to be updated.
|
||||
*/
|
||||
protected abstract void updateConnectButtonState();
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// Initialize the PlusClient connection.
|
||||
// Scopes indicate the information about the user your application will be able to access.
|
||||
mPlusClient =
|
||||
new PlusClient.Builder(this, this, this).setScopes(Scopes.PLUS_LOGIN,
|
||||
Scopes.PLUS_ME).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to sign in the user.
|
||||
*/
|
||||
public void signIn() {
|
||||
if (!mPlusClient.isConnected()) {
|
||||
// Show the dialog as we are now signing in.
|
||||
setProgressBarVisible(true);
|
||||
// Make sure that we will start the resolution (e.g. fire the intent and pop up a
|
||||
// dialog for the user) for any errors that come in.
|
||||
mAutoResolveOnFail = true;
|
||||
// We should always have a connection result ready to resolve,
|
||||
// so we can start that process.
|
||||
if (mConnectionResult != null) {
|
||||
startResolution();
|
||||
} else {
|
||||
// If we don't have one though, we can start connect in
|
||||
// order to retrieve one.
|
||||
initiatePlusClientConnect();
|
||||
}
|
||||
}
|
||||
|
||||
updateConnectButtonState();
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect the {@link PlusClient} only if a connection isn't already in progress. This will
|
||||
* call back to {@link #onConnected(android.os.Bundle)} or
|
||||
* {@link #onConnectionFailed(com.google.android.gms.common.ConnectionResult)}.
|
||||
*/
|
||||
private void initiatePlusClientConnect() {
|
||||
if (!mPlusClient.isConnected() && !mPlusClient.isConnecting()) {
|
||||
mPlusClient.connect();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect the {@link PlusClient} only if it is connected (otherwise, it can throw an error.)
|
||||
* This will call back to {@link #onDisconnected()}.
|
||||
*/
|
||||
private void initiatePlusClientDisconnect() {
|
||||
if (mPlusClient.isConnected()) {
|
||||
mPlusClient.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign out the user (so they can switch to another account).
|
||||
*/
|
||||
public void signOut() {
|
||||
|
||||
// We only want to sign out if we're connected.
|
||||
if (mPlusClient.isConnected()) {
|
||||
// Clear the default account in order to allow the user to potentially choose a
|
||||
// different account from the account chooser.
|
||||
mPlusClient.clearDefaultAccount();
|
||||
|
||||
// Disconnect from Google Play Services, then reconnect in order to restart the
|
||||
// process from scratch.
|
||||
initiatePlusClientDisconnect();
|
||||
|
||||
Log.v(TAG, "Sign out successful!");
|
||||
}
|
||||
|
||||
updateConnectButtonState();
|
||||
}
|
||||
|
||||
/**
|
||||
* Revoke Google+ authorization completely.
|
||||
*/
|
||||
public void revokeAccess() {
|
||||
|
||||
if (mPlusClient.isConnected()) {
|
||||
// Clear the default account as in the Sign Out.
|
||||
mPlusClient.clearDefaultAccount();
|
||||
|
||||
// Revoke access to this entire application. This will call back to
|
||||
// onAccessRevoked when it is complete, as it needs to reach the Google
|
||||
// authentication servers to revoke all tokens.
|
||||
mPlusClient.revokeAccessAndDisconnect(new PlusClient.OnAccessRevokedListener() {
|
||||
public void onAccessRevoked(ConnectionResult result) {
|
||||
updateConnectButtonState();
|
||||
onPlusClientRevokeAccess();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
initiatePlusClientConnect();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
initiatePlusClientDisconnect();
|
||||
}
|
||||
|
||||
public boolean isPlusClientConnecting() {
|
||||
return mPlusClientIsConnecting;
|
||||
}
|
||||
|
||||
private void setProgressBarVisible(boolean flag) {
|
||||
mPlusClientIsConnecting = flag;
|
||||
onPlusClientBlockingUI(flag);
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper method to flip the mResolveOnFail flag and start the resolution
|
||||
* of the ConnectionResult from the failed connect() call.
|
||||
*/
|
||||
private void startResolution() {
|
||||
try {
|
||||
// Don't start another resolution now until we have a result from the activity we're
|
||||
// about to start.
|
||||
mAutoResolveOnFail = false;
|
||||
// If we can resolve the error, then call start resolution and pass it an integer tag
|
||||
// we can use to track.
|
||||
// This means that when we get the onActivityResult callback we'll know it's from
|
||||
// being started here.
|
||||
mConnectionResult.startResolutionForResult(this, OUR_REQUEST_CODE);
|
||||
} catch (IntentSender.SendIntentException e) {
|
||||
// Any problems, just try to connect() again so we get a new ConnectionResult.
|
||||
mConnectionResult = null;
|
||||
initiatePlusClientConnect();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An earlier connection failed, and we're now receiving the result of the resolution attempt
|
||||
* by PlusClient.
|
||||
*
|
||||
* @see #onConnectionFailed(ConnectionResult)
|
||||
*/
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int responseCode, Intent intent) {
|
||||
updateConnectButtonState();
|
||||
if (requestCode == OUR_REQUEST_CODE && responseCode == RESULT_OK) {
|
||||
// If we have a successful result, we will want to be able to resolve any further
|
||||
// errors, so turn on resolution with our flag.
|
||||
mAutoResolveOnFail = true;
|
||||
// If we have a successful result, let's call connect() again. If there are any more
|
||||
// errors to resolve we'll get our onConnectionFailed, but if not,
|
||||
// we'll get onConnected.
|
||||
initiatePlusClientConnect();
|
||||
} else if (requestCode == OUR_REQUEST_CODE && responseCode != RESULT_OK) {
|
||||
// If we've got an error we can't resolve, we're no longer in the midst of signing
|
||||
// in, so we can stop the progress spinner.
|
||||
setProgressBarVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Successfully connected (called by PlusClient)
|
||||
*/
|
||||
@Override
|
||||
public void onConnected(Bundle connectionHint) {
|
||||
updateConnectButtonState();
|
||||
setProgressBarVisible(false);
|
||||
onPlusClientSignIn();
|
||||
}
|
||||
|
||||
/**
|
||||
* Successfully disconnected (called by PlusClient)
|
||||
*/
|
||||
@Override
|
||||
public void onDisconnected() {
|
||||
updateConnectButtonState();
|
||||
onPlusClientSignOut();
|
||||
}
|
||||
|
||||
/**
|
||||
* Connection failed for some reason (called by PlusClient)
|
||||
* Try and resolve the result. Failure here is usually not an indication of a serious error,
|
||||
* just that the user's input is needed.
|
||||
*
|
||||
* @see #onActivityResult(int, int, Intent)
|
||||
*/
|
||||
@Override
|
||||
public void onConnectionFailed(ConnectionResult result) {
|
||||
updateConnectButtonState();
|
||||
|
||||
// Most of the time, the connection will fail with a user resolvable result. We can store
|
||||
// that in our mConnectionResult property ready to be used when the user clicks the
|
||||
// sign-in button.
|
||||
if (result.hasResolution()) {
|
||||
mConnectionResult = result;
|
||||
if (mAutoResolveOnFail) {
|
||||
// This is a local helper function that starts the resolution of the problem,
|
||||
// which may be showing the user an account chooser or similar.
|
||||
startResolution();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public PlusClient getPlusClient() {
|
||||
return mPlusClient;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,68 @@
|
||||
<?xml version="1.0"?>
|
||||
<template
|
||||
format="4"
|
||||
revision="5"
|
||||
name="Login Activity"
|
||||
description="Creates a new login activity, allowing users to optionally sign in with Google+ or enter an email address and password to log in to or register with your application."
|
||||
minApi="8"
|
||||
minBuildApi="14">
|
||||
|
||||
<dependency name="android-support-v4" revision="8" />
|
||||
|
||||
<category value="Activity" />
|
||||
<formfactor value="Mobile" />
|
||||
|
||||
<parameter
|
||||
id="activityClass"
|
||||
name="Activity Name"
|
||||
type="string"
|
||||
constraints="class|unique|nonempty"
|
||||
default="LoginActivity"
|
||||
help="The name of the activity class to create" />
|
||||
|
||||
<parameter
|
||||
id="layoutName"
|
||||
name="Layout Name"
|
||||
type="string"
|
||||
constraints="layout|unique|nonempty"
|
||||
suggest="${activityToLayout(activityClass)}"
|
||||
default="activity_login"
|
||||
help="The name of the layout to create for the activity" />
|
||||
|
||||
<parameter
|
||||
id="activityTitle"
|
||||
name="Title"
|
||||
type="string"
|
||||
constraints="nonempty"
|
||||
default="Sign in"
|
||||
help="The name of the activity." />
|
||||
|
||||
<parameter
|
||||
id="parentActivityClass"
|
||||
name="Hierarchical Parent"
|
||||
type="string"
|
||||
constraints="activity|exists|empty"
|
||||
default=""
|
||||
help="The hierarchical parent activity, used to provide a default implementation for the 'Up' button" />
|
||||
|
||||
<parameter
|
||||
id="packageName"
|
||||
name="Package name"
|
||||
type="string"
|
||||
constraints="package"
|
||||
default="com.mycompany.myapp" />
|
||||
|
||||
<parameter
|
||||
id="includeGooglePlus"
|
||||
name="Include Google+ sign in"
|
||||
type="boolean"
|
||||
default="true" />
|
||||
|
||||
<thumbs>
|
||||
<thumb>template_login_activity.png</thumb>
|
||||
</thumbs>
|
||||
|
||||
<globals file="globals.xml.ftl" />
|
||||
<execute file="recipe.xml.ftl" />
|
||||
|
||||
</template>
|
After Width: | Height: | Size: 5.8 KiB |
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0"?>
|
||||
<globals>
|
||||
<global id="appCompat" type="boolean" value="${(minApiLevel lt 14)?string}" />
|
||||
<global id="Support" value="${(minApiLevel lt 14)?string('Support','')}" />
|
||||
<global id="projectOut" value="." />
|
||||
<global id="manifestOut" value="${manifestDir}" />
|
||||
<global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
|
||||
<global id="resOut" value="${resDir}" />
|
||||
<global id="CollectionName" value="${extractLetters(objectKind)}List" />
|
||||
<global id="collection_name" value="${extractLetters(objectKind?lower_case)}_list" />
|
||||
<global id="DetailName" value="${extractLetters(objectKind)}Detail" />
|
||||
<global id="detail_name" value="${extractLetters(objectKind?lower_case)}_detail" />
|
||||
<global id="relativePackage" value="<#if relativePackage?has_content>${relativePackage}<#else>${packageName}</#if>" />
|
||||
</globals>
|
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0"?>
|
||||
<recipe>
|
||||
<dependency mavenUrl="com.android.support:support-v4:19.+" />
|
||||
|
||||
<merge from="AndroidManifest.xml.ftl"
|
||||
to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
|
||||
|
||||
<merge from="res/values-large/refs.xml.ftl"
|
||||
to="${escapeXmlAttribute(resOut)}/values-large/refs.xml" />
|
||||
<merge from="res/values-sw600dp/refs.xml.ftl"
|
||||
to="${escapeXmlAttribute(resOut)}/values-sw600dp/refs.xml" />
|
||||
<merge from="res/values/strings.xml.ftl"
|
||||
to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
|
||||
|
||||
<instantiate from="res/layout/activity_content_detail.xml.ftl"
|
||||
to="${escapeXmlAttribute(resOut)}/layout/activity_${detail_name}.xml" />
|
||||
<instantiate from="res/layout/activity_content_list.xml.ftl"
|
||||
to="${escapeXmlAttribute(resOut)}/layout/activity_${collection_name}.xml" />
|
||||
<instantiate from="res/layout/activity_content_twopane.xml.ftl"
|
||||
to="${escapeXmlAttribute(resOut)}/layout/activity_${extractLetters(objectKind?lower_case)}_twopane.xml" />
|
||||
<instantiate from="res/layout/fragment_content_detail.xml.ftl"
|
||||
to="${escapeXmlAttribute(resOut)}/layout/fragment_${detail_name}.xml" />
|
||||
|
||||
<instantiate from="src/app_package/ContentDetailActivity.java.ftl"
|
||||
to="${escapeXmlAttribute(srcOut)}/${DetailName}Activity.java" />
|
||||
<instantiate from="src/app_package/ContentDetailFragment.java.ftl"
|
||||
to="${escapeXmlAttribute(srcOut)}/${DetailName}Fragment.java" />
|
||||
<instantiate from="src/app_package/ContentListActivity.java.ftl"
|
||||
to="${escapeXmlAttribute(srcOut)}/${CollectionName}Activity.java" />
|
||||
<instantiate from="src/app_package/ContentListFragment.java.ftl"
|
||||
to="${escapeXmlAttribute(srcOut)}/${CollectionName}Fragment.java" />
|
||||
<instantiate from="src/app_package/dummy/DummyContent.java.ftl"
|
||||
to="${escapeXmlAttribute(srcOut)}/dummy/DummyContent.java" />
|
||||
|
||||
<open file="${escapeXmlAttribute(srcOut)}/${DetailName}Fragment.java" />
|
||||
<open file="${escapeXmlAttribute(resOut)}/layout/fragment_${detail_name}.xml" />
|
||||
</recipe>
|
@@ -0,0 +1,31 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<application>
|
||||
<activity android:name="${relativePackage}.${CollectionName}Activity"
|
||||
<#if isNewProject>
|
||||
android:label="@string/app_name"
|
||||
<#else>
|
||||
android:label="@string/title_${collection_name}"
|
||||
</#if>
|
||||
<#if buildApi gte 16 && parentActivityClass != "">android:parentActivityName="${parentActivityClass}"</#if>>
|
||||
<#if parentActivityClass != "">
|
||||
<meta-data android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="${parentActivityClass}" />
|
||||
</#if>
|
||||
<#if isLauncher>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</#if>
|
||||
</activity>
|
||||
|
||||
<activity android:name="${relativePackage}.${DetailName}Activity"
|
||||
android:label="@string/title_${detail_name}"
|
||||
<#if buildApi gte 16>android:parentActivityName="${relativePackage}.${CollectionName}Activity"</#if>>
|
||||
<meta-data android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="${relativePackage}.${CollectionName}Activity" />
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
@@ -0,0 +1,7 @@
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/${detail_name}_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context="${relativePackage}.${DetailName}Activity"
|
||||
tools:ignore="MergeRootFrame" />
|
@@ -0,0 +1,10 @@
|
||||
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/${collection_name}"
|
||||
android:name="${packageName}.${CollectionName}Fragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
tools:context="${relativePackage}.${CollectionName}Activity"
|
||||
tools:layout="@android:layout/list_content" />
|
@@ -0,0 +1,38 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:baselineAligned="false"
|
||||
android:divider="?android:attr/dividerHorizontal"
|
||||
android:orientation="horizontal"
|
||||
android:showDividers="middle"
|
||||
tools:context="${relativePackage}.${CollectionName}Activity">
|
||||
|
||||
<!--
|
||||
This layout is a two-pane layout for the ${objectKindPlural}
|
||||
master/detail flow. See res/values-large/refs.xml and
|
||||
res/values-sw600dp/refs.xml for an example of layout aliases
|
||||
that replace the single-pane version of the layout with
|
||||
this two-pane version.
|
||||
|
||||
For more on layout aliases, see:
|
||||
http://developer.android.com/training/multiscreen/screensizes.html#TaskUseAliasFilters
|
||||
-->
|
||||
|
||||
<fragment
|
||||
android:id="@+id/${collection_name}"
|
||||
android:name="${packageName}.${CollectionName}Fragment"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
tools:layout="@android:layout/list_content" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/${detail_name}_container"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="3" />
|
||||
|
||||
</LinearLayout>
|
@@ -0,0 +1,9 @@
|
||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/${detail_name}"
|
||||
style="?android:attr/textAppearanceLarge"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="16dp"
|
||||
android:textIsSelectable="true"
|
||||
tools:context="${relativePackage}.${DetailName}Fragment" />
|
@@ -0,0 +1,10 @@
|
||||
<resources>
|
||||
<!--
|
||||
Layout alias to replace the single-pane version of the layout with a
|
||||
two-pane version on Large screens.
|
||||
|
||||
For more on layout aliases, see:
|
||||
http://developer.android.com/training/multiscreen/screensizes.html#TaskUseAliasFilters
|
||||
-->
|
||||
<item type="layout" name="activity_${collection_name}">@layout/activity_${extractLetters(objectKind?lower_case)}_twopane</item>
|
||||
</resources>
|
@@ -0,0 +1,11 @@
|
||||
<resources>
|
||||
<!--
|
||||
Layout alias to replace the single-pane version of the layout with a
|
||||
two-pane version on screens with a smallest width (smallest dimension)
|
||||
of at least 600 density-independent pixels (dips).
|
||||
|
||||
For more on layout aliases, see:
|
||||
http://developer.android.com/training/multiscreen/screensizes.html#TaskUseAliasFilters
|
||||
-->
|
||||
<item type="layout" name="activity_${collection_name}">@layout/activity_${extractLetters(objectKind?lower_case)}_twopane</item>
|
||||
</resources>
|
@@ -0,0 +1,6 @@
|
||||
<resources>
|
||||
<#if !isNewProject>
|
||||
<string name="title_${collection_name}">${escapeXmlString(objectKindPlural)}</string>
|
||||
</#if>
|
||||
<string name="title_${detail_name}">${escapeXmlString(objectKind)} Detail</string>
|
||||
</resources>
|
@@ -0,0 +1,78 @@
|
||||
package ${packageName};
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import <#if appCompat>android.support.v7.app.ActionBarActivity<#else>android.app.Activity</#if>;
|
||||
<#if minApiLevel lt 16>import android.support.v4.app.NavUtils;</#if>
|
||||
import android.view.MenuItem;
|
||||
<#if applicationPackage??>import ${applicationPackage}.R;</#if>
|
||||
|
||||
/**
|
||||
* An activity representing a single ${objectKind} detail screen. This
|
||||
* activity is only used on handset devices. On tablet-size devices,
|
||||
* item details are presented side-by-side with a list of items
|
||||
* in a {@link ${CollectionName}Activity}.
|
||||
* <p>
|
||||
* This activity is mostly just a 'shell' activity containing nothing
|
||||
* more than a {@link ${DetailName}Fragment}.
|
||||
*/
|
||||
public class ${DetailName}Activity extends ${appCompat?string('ActionBar','')}Activity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_${detail_name});
|
||||
|
||||
// Show the Up button in the action bar.
|
||||
get${Support}ActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
// savedInstanceState is non-null when there is fragment state
|
||||
// saved from previous configurations of this activity
|
||||
// (e.g. when rotating the screen from portrait to landscape).
|
||||
// In this case, the fragment will automatically be re-added
|
||||
// to its container so we don't need to manually add it.
|
||||
// For more information, see the Fragments API guide at:
|
||||
//
|
||||
// http://developer.android.com/guide/components/fragments.html
|
||||
//
|
||||
if (savedInstanceState == null) {
|
||||
// Create the detail fragment and add it to the activity
|
||||
// using a fragment transaction.
|
||||
Bundle arguments = new Bundle();
|
||||
arguments.putString(${DetailName}Fragment.ARG_ITEM_ID,
|
||||
getIntent().getStringExtra(${DetailName}Fragment.ARG_ITEM_ID));
|
||||
${DetailName}Fragment fragment = new ${DetailName}Fragment();
|
||||
fragment.setArguments(arguments);
|
||||
get${Support}FragmentManager().beginTransaction()
|
||||
.add(R.id.${detail_name}_container, fragment)
|
||||
.commit();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
int id = item.getItemId();
|
||||
if (id == android.R.id.home) {
|
||||
<#if minApiLevel lt 16>
|
||||
// This ID represents the Home or Up button. In the case of this
|
||||
// activity, the Up button is shown. Use NavUtils to allow users
|
||||
// to navigate up one level in the application structure. For
|
||||
// more details, see the Navigation pattern on Android Design:
|
||||
//
|
||||
// http://developer.android.com/design/patterns/navigation.html#up-vs-back
|
||||
//
|
||||
NavUtils.navigateUpTo(this, new Intent(this, ${CollectionName}Activity.class));
|
||||
<#else>
|
||||
// This ID represents the Home or Up button. In the case of this
|
||||
// activity, the Up button is shown. For
|
||||
// more details, see the Navigation pattern on Android Design:
|
||||
//
|
||||
// http://developer.android.com/design/patterns/navigation.html#up-vs-back
|
||||
//
|
||||
navigateUpTo(new Intent(this, ${CollectionName}Activity.class));
|
||||
</#if>
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
@@ -0,0 +1,62 @@
|
||||
package ${packageName};
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.<#if appCompat>support.v4.</#if>app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
<#if applicationPackage??>import ${applicationPackage}.R;</#if>
|
||||
|
||||
import ${packageName}.dummy.DummyContent;
|
||||
|
||||
/**
|
||||
* A fragment representing a single ${objectKind} detail screen.
|
||||
* This fragment is either contained in a {@link ${CollectionName}Activity}
|
||||
* in two-pane mode (on tablets) or a {@link ${DetailName}Activity}
|
||||
* on handsets.
|
||||
*/
|
||||
public class ${DetailName}Fragment extends Fragment {
|
||||
/**
|
||||
* The fragment argument representing the item ID that this fragment
|
||||
* represents.
|
||||
*/
|
||||
public static final String ARG_ITEM_ID = "item_id";
|
||||
|
||||
/**
|
||||
* The dummy content this fragment is presenting.
|
||||
*/
|
||||
private DummyContent.DummyItem mItem;
|
||||
|
||||
/**
|
||||
* Mandatory empty constructor for the fragment manager to instantiate the
|
||||
* fragment (e.g. upon screen orientation changes).
|
||||
*/
|
||||
public ${DetailName}Fragment() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
if (getArguments().containsKey(ARG_ITEM_ID)) {
|
||||
// Load the dummy content specified by the fragment
|
||||
// arguments. In a real-world scenario, use a Loader
|
||||
// to load content from a content provider.
|
||||
mItem = DummyContent.ITEM_MAP.get(getArguments().getString(ARG_ITEM_ID));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
View rootView = inflater.inflate(R.layout.fragment_${detail_name}, container, false);
|
||||
|
||||
// Show the dummy content as text in a TextView.
|
||||
if (mItem != null) {
|
||||
((TextView) rootView.findViewById(R.id.${detail_name})).setText(mItem.content);
|
||||
}
|
||||
|
||||
return rootView;
|
||||
}
|
||||
}
|
@@ -0,0 +1,106 @@
|
||||
package ${packageName};
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import <#if appCompat>android.support.v4.app.FragmentActivity<#else>android.app.Activity</#if>;
|
||||
<#if (parentActivityClass != "" && minApiLevel lt 16)>import android.support.v4.app.NavUtils;</#if>
|
||||
<#if parentActivityClass != "">import android.view.MenuItem;</#if>
|
||||
<#if applicationPackage??>import ${applicationPackage}.R;</#if>
|
||||
|
||||
/**
|
||||
* An activity representing a list of ${objectKindPlural}. This activity
|
||||
* has different presentations for handset and tablet-size devices. On
|
||||
* handsets, the activity presents a list of items, which when touched,
|
||||
* lead to a {@link ${DetailName}Activity} representing
|
||||
* item details. On tablets, the activity presents the list of items and
|
||||
* item details side-by-side using two vertical panes.
|
||||
* <p>
|
||||
* The activity makes heavy use of fragments. The list of items is a
|
||||
* {@link ${CollectionName}Fragment} and the item details
|
||||
* (if present) is a {@link ${DetailName}Fragment}.
|
||||
* <p>
|
||||
* This activity also implements the required
|
||||
* {@link ${CollectionName}Fragment.Callbacks} interface
|
||||
* to listen for item selections.
|
||||
*/
|
||||
public class ${CollectionName}Activity extends ${(appCompat)?string('Fragment','')}Activity
|
||||
implements ${CollectionName}Fragment.Callbacks {
|
||||
|
||||
/**
|
||||
* Whether or not the activity is in two-pane mode, i.e. running on a tablet
|
||||
* device.
|
||||
*/
|
||||
private boolean mTwoPane;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_${collection_name});
|
||||
<#if parentActivityClass != "">
|
||||
// Show the Up button in the action bar.
|
||||
get${Support}ActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
</#if>
|
||||
|
||||
if (findViewById(R.id.${detail_name}_container) != null) {
|
||||
// The detail container view will be present only in the
|
||||
// large-screen layouts (res/values-large and
|
||||
// res/values-sw600dp). If this view is present, then the
|
||||
// activity should be in two-pane mode.
|
||||
mTwoPane = true;
|
||||
|
||||
// In two-pane mode, list items should be given the
|
||||
// 'activated' state when touched.
|
||||
((${CollectionName}Fragment) get${Support}FragmentManager()
|
||||
.findFragmentById(R.id.${collection_name}))
|
||||
.setActivateOnItemClick(true);
|
||||
}
|
||||
|
||||
// TODO: If exposing deep links into your app, handle intents here.
|
||||
}
|
||||
<#if parentActivityClass != "">
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
int id = item.getItemId();
|
||||
if (id == android.R.id.home) {
|
||||
// This ID represents the Home or Up button. In the case of this
|
||||
// activity, the Up button is shown. Use NavUtils to allow users
|
||||
// to navigate up one level in the application structure. For
|
||||
// more details, see the Navigation pattern on Android Design:
|
||||
//
|
||||
// http://developer.android.com/design/patterns/navigation.html#up-vs-back
|
||||
//
|
||||
${(minApiLevel lt 16)?string('NavUtils.','')}navigateUpFromSameTask(this);
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
</#if>
|
||||
|
||||
/**
|
||||
* Callback method from {@link ${CollectionName}Fragment.Callbacks}
|
||||
* indicating that the item with the given ID was selected.
|
||||
*/
|
||||
@Override
|
||||
public void onItemSelected(String id) {
|
||||
if (mTwoPane) {
|
||||
// In two-pane mode, show the detail view in this activity by
|
||||
// adding or replacing the detail fragment using a
|
||||
// fragment transaction.
|
||||
Bundle arguments = new Bundle();
|
||||
arguments.putString(${DetailName}Fragment.ARG_ITEM_ID, id);
|
||||
${DetailName}Fragment fragment = new ${DetailName}Fragment();
|
||||
fragment.setArguments(arguments);
|
||||
get${Support}FragmentManager().beginTransaction()
|
||||
.replace(R.id.${detail_name}_container, fragment)
|
||||
.commit();
|
||||
|
||||
} else {
|
||||
// In single-pane mode, simply start the detail activity
|
||||
// for the selected item ID.
|
||||
Intent detailIntent = new Intent(this, ${DetailName}Activity.class);
|
||||
detailIntent.putExtra(${DetailName}Fragment.ARG_ITEM_ID, id);
|
||||
startActivity(detailIntent);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,152 @@
|
||||
package ${packageName};
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.<#if Support?has_content>support.v4.</#if>app.ListFragment;
|
||||
import android.view.View;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ListView;
|
||||
<#if applicationPackage??>import ${applicationPackage}.R;</#if>
|
||||
|
||||
import ${packageName}.dummy.DummyContent;
|
||||
|
||||
/**
|
||||
* A list fragment representing a list of ${objectKindPlural}. This fragment
|
||||
* also supports tablet devices by allowing list items to be given an
|
||||
* 'activated' state upon selection. This helps indicate which item is
|
||||
* currently being viewed in a {@link ${DetailName}Fragment}.
|
||||
* <p>
|
||||
* Activities containing this fragment MUST implement the {@link Callbacks}
|
||||
* interface.
|
||||
*/
|
||||
public class ${CollectionName}Fragment extends ListFragment {
|
||||
|
||||
/**
|
||||
* The serialization (saved instance state) Bundle key representing the
|
||||
* activated item position. Only used on tablets.
|
||||
*/
|
||||
private static final String STATE_ACTIVATED_POSITION = "activated_position";
|
||||
|
||||
/**
|
||||
* The fragment's current callback object, which is notified of list item
|
||||
* clicks.
|
||||
*/
|
||||
private Callbacks mCallbacks = sDummyCallbacks;
|
||||
|
||||
/**
|
||||
* The current activated item position. Only used on tablets.
|
||||
*/
|
||||
private int mActivatedPosition = ListView.INVALID_POSITION;
|
||||
|
||||
/**
|
||||
* A callback interface that all activities containing this fragment must
|
||||
* implement. This mechanism allows activities to be notified of item
|
||||
* selections.
|
||||
*/
|
||||
public interface Callbacks {
|
||||
/**
|
||||
* Callback for when an item has been selected.
|
||||
*/
|
||||
public void onItemSelected(String id);
|
||||
}
|
||||
|
||||
/**
|
||||
* A dummy implementation of the {@link Callbacks} interface that does
|
||||
* nothing. Used only when this fragment is not attached to an activity.
|
||||
*/
|
||||
private static Callbacks sDummyCallbacks = new Callbacks() {
|
||||
@Override
|
||||
public void onItemSelected(String id) {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Mandatory empty constructor for the fragment manager to instantiate the
|
||||
* fragment (e.g. upon screen orientation changes).
|
||||
*/
|
||||
public ${CollectionName}Fragment() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// TODO: replace with a real list adapter.
|
||||
setListAdapter(new ArrayAdapter<DummyContent.DummyItem>(
|
||||
getActivity(),
|
||||
android.R.layout.simple_list_item_activated_1,
|
||||
android.R.id.text1,
|
||||
DummyContent.ITEMS));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
// Restore the previously serialized activated item position.
|
||||
if (savedInstanceState != null
|
||||
&& savedInstanceState.containsKey(STATE_ACTIVATED_POSITION)) {
|
||||
setActivatedPosition(savedInstanceState.getInt(STATE_ACTIVATED_POSITION));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
|
||||
// Activities containing this fragment must implement its callbacks.
|
||||
if (!(activity instanceof Callbacks)) {
|
||||
throw new IllegalStateException("Activity must implement fragment's callbacks.");
|
||||
}
|
||||
|
||||
mCallbacks = (Callbacks) activity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetach() {
|
||||
super.onDetach();
|
||||
|
||||
// Reset the active callbacks interface to the dummy implementation.
|
||||
mCallbacks = sDummyCallbacks;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onListItemClick(ListView listView, View view, int position, long id) {
|
||||
super.onListItemClick(listView, view, position, id);
|
||||
|
||||
// Notify the active callbacks interface (the activity, if the
|
||||
// fragment is attached to one) that an item has been selected.
|
||||
mCallbacks.onItemSelected(DummyContent.ITEMS.get(position).id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
if (mActivatedPosition != ListView.INVALID_POSITION) {
|
||||
// Serialize and persist the activated item position.
|
||||
outState.putInt(STATE_ACTIVATED_POSITION, mActivatedPosition);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns on activate-on-click mode. When this mode is on, list items will be
|
||||
* given the 'activated' state when touched.
|
||||
*/
|
||||
public void setActivateOnItemClick(boolean activateOnItemClick) {
|
||||
// When setting CHOICE_MODE_SINGLE, ListView will automatically
|
||||
// give items the 'activated' state when touched.
|
||||
getListView().setChoiceMode(activateOnItemClick
|
||||
? ListView.CHOICE_MODE_SINGLE
|
||||
: ListView.CHOICE_MODE_NONE);
|
||||
}
|
||||
|
||||
private void setActivatedPosition(int position) {
|
||||
if (position == ListView.INVALID_POSITION) {
|
||||
getListView().setItemChecked(mActivatedPosition, false);
|
||||
} else {
|
||||
getListView().setItemChecked(position, true);
|
||||
}
|
||||
|
||||
mActivatedPosition = position;
|
||||
}
|
||||
}
|
@@ -0,0 +1,55 @@
|
||||
package ${packageName}.dummy;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Helper class for providing sample content for user interfaces created by
|
||||
* Android template wizards.
|
||||
* <p>
|
||||
* TODO: Replace all uses of this class before publishing your app.
|
||||
*/
|
||||
public class DummyContent {
|
||||
|
||||
/**
|
||||
* An array of sample (dummy) items.
|
||||
*/
|
||||
public static List<DummyItem> ITEMS = new ArrayList<DummyItem>();
|
||||
|
||||
/**
|
||||
* A map of sample (dummy) items, by ID.
|
||||
*/
|
||||
public static Map<String, DummyItem> ITEM_MAP = new HashMap<String, DummyItem>();
|
||||
|
||||
static {
|
||||
// Add 3 sample items.
|
||||
addItem(new DummyItem("1", "Item 1"));
|
||||
addItem(new DummyItem("2", "Item 2"));
|
||||
addItem(new DummyItem("3", "Item 3"));
|
||||
}
|
||||
|
||||
private static void addItem(DummyItem item) {
|
||||
ITEMS.add(item);
|
||||
ITEM_MAP.put(item.id, item);
|
||||
}
|
||||
|
||||
/**
|
||||
* A dummy item representing a piece of content.
|
||||
*/
|
||||
public static class DummyItem {
|
||||
public String id;
|
||||
public String content;
|
||||
|
||||
public DummyItem(String id, String content) {
|
||||
this.id = id;
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return content;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,68 @@
|
||||
<?xml version="1.0"?>
|
||||
<template
|
||||
format="4"
|
||||
revision="5"
|
||||
name="Master/Detail Flow"
|
||||
minApi="4"
|
||||
description="Creates a new master/detail flow, allowing users to view a collection of objects as well as details for each object. This flow is presented using two columns on tablet-size screens and one column on handsets and smaller screens. This template creates two activities, a master fragment, and a detail fragment."
|
||||
category="Activity">
|
||||
|
||||
<dependency name="android-support-v4" revision="8" />
|
||||
|
||||
<category value="Activity" />
|
||||
<formfactor value="Mobile" />
|
||||
|
||||
<thumbs>
|
||||
<thumb>template_master_detail.png</thumb>
|
||||
</thumbs>
|
||||
|
||||
<parameter
|
||||
id="objectKind"
|
||||
name="Object Kind"
|
||||
type="string"
|
||||
constraints="nonempty"
|
||||
default="Item"
|
||||
help="Other examples are 'Person', 'Book', etc." />
|
||||
|
||||
<parameter
|
||||
id="objectKindPlural"
|
||||
name="Object Kind Plural"
|
||||
type="string"
|
||||
constraints="nonempty"
|
||||
default="Items"
|
||||
help="Other examples are 'People', 'Books', etc." />
|
||||
|
||||
<parameter
|
||||
id="activityTitle"
|
||||
name="Title"
|
||||
type="string"
|
||||
constraints="nonempty"
|
||||
suggest="${objectKindPlural}"
|
||||
default="Items" />
|
||||
|
||||
<parameter
|
||||
id="isLauncher"
|
||||
name="Launcher Activity"
|
||||
type="boolean"
|
||||
default="false"
|
||||
help="If true, the primary activity in the flow will have a CATEGORY_LAUNCHER intent filter, making it visible in the launcher" />
|
||||
|
||||
<parameter
|
||||
id="parentActivityClass"
|
||||
name="Hierarchical Parent"
|
||||
type="string"
|
||||
constraints="activity|exists|empty"
|
||||
default=""
|
||||
help="The hierarchical parent activity, used to provide a default implementation for the 'Up' button" />
|
||||
|
||||
<parameter
|
||||
id="packageName"
|
||||
name="Package name"
|
||||
type="string"
|
||||
constraints="package"
|
||||
default="com.mycompany.myapp" />
|
||||
|
||||
<globals file="globals.xml.ftl" />
|
||||
<execute file="recipe.xml.ftl" />
|
||||
|
||||
</template>
|
After Width: | Height: | Size: 7.0 KiB |
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0"?>
|
||||
<globals>
|
||||
<global id="manifestOut" value="${manifestDir}" />
|
||||
<global id="appCompat" type="boolean" value="${(minApiLevel lt 14)?string}" />
|
||||
<!-- e.g. getSupportActionBar vs. getActionBar -->
|
||||
<global id="Support" value="${(minApiLevel lt 14)?string('Support','')}" />
|
||||
<global id="ActionNamespace" value="${(minApiLevel lt 14)?string('app','android')}" />
|
||||
<global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
|
||||
<global id="resOut" value="${resDir}" />
|
||||
<global id="menuName" value="${classToResource(activityClass)}" />
|
||||
<global id="relativePackage" value="<#if relativePackage?has_content>${relativePackage}<#else>${packageName}</#if>" />
|
||||
</globals>
|
@@ -0,0 +1,49 @@
|
||||
<?xml version="1.0"?>
|
||||
<recipe>
|
||||
|
||||
<#if appCompat><dependency mavenUrl="com.android.support:appcompat-v7:19.+"/></#if>
|
||||
<#if !appCompat><dependency mavenUrl="com.android.support:support-v4:19.+"/></#if>
|
||||
|
||||
<merge from="AndroidManifest.xml.ftl"
|
||||
to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
|
||||
|
||||
<instantiate from="res/menu/main.xml.ftl"
|
||||
to="${escapeXmlAttribute(resOut)}/menu/${menuName}.xml" />
|
||||
|
||||
<merge from="res/values/strings.xml.ftl"
|
||||
to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
|
||||
|
||||
<merge from="res/values/dimens.xml.ftl"
|
||||
to="${escapeXmlAttribute(resOut)}/values/dimens.xml" />
|
||||
<merge from="res/values-w820dp/dimens.xml"
|
||||
to="${escapeXmlAttribute(resOut)}/values-w820dp/dimens.xml" />
|
||||
|
||||
<!-- TODO: switch on Holo Dark v. Holo Light -->
|
||||
<copy from="res/drawable-hdpi"
|
||||
to="${escapeXmlAttribute(resOut)}/drawable-hdpi" />
|
||||
<copy from="res/drawable-mdpi"
|
||||
to="${escapeXmlAttribute(resOut)}/drawable-mdpi" />
|
||||
<copy from="res/drawable-xhdpi"
|
||||
to="${escapeXmlAttribute(resOut)}/drawable-xhdpi" />
|
||||
<copy from="res/drawable-xxhdpi"
|
||||
to="${escapeXmlAttribute(resOut)}/drawable-xxhdpi" />
|
||||
|
||||
<instantiate from="res/menu/global.xml.ftl"
|
||||
to="${escapeXmlAttribute(resOut)}/menu/global.xml" />
|
||||
|
||||
<instantiate from="res/layout/activity_drawer.xml.ftl"
|
||||
to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
|
||||
<instantiate from="res/layout/fragment_navigation_drawer.xml.ftl"
|
||||
to="${escapeXmlAttribute(resOut)}/layout/${navigationDrawerLayout}.xml" />
|
||||
|
||||
<instantiate from="res/layout/fragment_simple.xml.ftl"
|
||||
to="${escapeXmlAttribute(resOut)}/layout/${fragmentLayoutName}.xml" />
|
||||
|
||||
<instantiate from="src/app_package/DrawerActivity.java.ftl"
|
||||
to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
|
||||
<instantiate from="src/app_package/NavigationDrawerFragment.java.ftl"
|
||||
to="${escapeXmlAttribute(srcOut)}/NavigationDrawerFragment.java" />
|
||||
|
||||
<open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
|
||||
<open file="${escapeXmlAttribute(resOut)}/layout/${fragmentLayoutName}.xml" />
|
||||
</recipe>
|
@@ -0,0 +1,24 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
|
||||
<application>
|
||||
<activity android:name="${relativePackage}.${activityClass}"
|
||||
<#if isNewProject>
|
||||
android:label="@string/app_name"
|
||||
<#else>
|
||||
android:label="@string/title_${activityToLayout(activityClass)}"
|
||||
</#if>
|
||||
<#if buildApi gte 16 && parentActivityClass != "">android:parentActivityName="${parentActivityClass}"</#if>>
|
||||
<#if parentActivityClass != "">
|
||||
<meta-data android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="${parentActivityClass}" />
|
||||
</#if>
|
||||
<#if isLauncher>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</#if>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
@@ -0,0 +1,7 @@
|
||||
dependencies {
|
||||
<#if dependencyList?? >
|
||||
<#list dependencyList as dependency>
|
||||
compile '${dependency}'
|
||||
</#list>
|
||||
</#if>
|
||||
}
|
After Width: | Height: | Size: 161 B |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 142 B |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 174 B |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 208 B |
After Width: | Height: | Size: 202 B |
@@ -0,0 +1,31 @@
|
||||
<!-- A DrawerLayout is intended to be used as the top-level content view using match_parent for both width and height to consume the full space available. -->
|
||||
<android.support.v4.widget.DrawerLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/drawer_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context="${relativePackage}.${activityClass}">
|
||||
|
||||
<!-- As the main content view, the view below consumes the entire
|
||||
space available using match_parent in both dimensions. -->
|
||||
<FrameLayout
|
||||
android:id="@+id/container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<!-- android:layout_gravity="start" tells DrawerLayout to treat
|
||||
this as a sliding drawer on the left side for left-to-right
|
||||
languages and on the right side for right-to-left languages.
|
||||
If you're not building against API 17 or higher, use
|
||||
android:layout_gravity="left" instead. -->
|
||||
<!-- The drawer is given a fixed width in dp and extends the full height of
|
||||
the container. -->
|
||||
<fragment android:id="@+id/navigation_drawer"
|
||||
android:layout_width="@dimen/navigation_drawer_width"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="<#if buildApi gte 17>start<#else>left</#if>"
|
||||
android:name="${packageName}.NavigationDrawerFragment"
|
||||
tools:layout="@layout/${navigationDrawerLayout}" />
|
||||
|
||||
</android.support.v4.widget.DrawerLayout>
|
@@ -0,0 +1,9 @@
|
||||
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:choiceMode="singleChoice"
|
||||
android:divider="@android:color/transparent"
|
||||
android:dividerHeight="0dp"
|
||||
android:background="#cccc"
|
||||
tools:context="${relativePackage}.NavigationDrawerFragment" />
|
@@ -0,0 +1,16 @@
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||
android:paddingTop="@dimen/activity_vertical_margin"
|
||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||
tools:context="${relativePackage}.${activityClass}$PlaceholderFragment">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/section_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</RelativeLayout>
|
@@ -0,0 +1,7 @@
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"<#if appCompat>
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"</#if>>
|
||||
<item android:id="@+id/action_settings"
|
||||
android:title="@string/action_settings"
|
||||
android:orderInCategory="100"
|
||||
${ActionNamespace}:showAsAction="never" />
|
||||
</menu>
|
@@ -0,0 +1,12 @@
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"<#if appCompat>
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"</#if>
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:context="${relativePackage}.${activityClass}" >
|
||||
<item android:id="@+id/action_example"
|
||||
android:title="@string/action_example"
|
||||
${ActionNamespace}:showAsAction="withText|ifRoom" />
|
||||
<item android:id="@+id/action_settings"
|
||||
android:title="@string/action_settings"
|
||||
android:orderInCategory="100"
|
||||
${ActionNamespace}:showAsAction="never" />
|
||||
</menu>
|
@@ -0,0 +1,6 @@
|
||||
<resources>
|
||||
<!-- Example customization of dimensions originally defined in res/values/dimens.xml
|
||||
(such as screen margins) for screens with more than 820dp of available width. This
|
||||
would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
|
||||
<dimen name="activity_horizontal_margin">64dp</dimen>
|
||||
</resources>
|
@@ -0,0 +1,9 @@
|
||||
<resources>
|
||||
<!-- Default screen margins, per the Android Design guidelines. -->
|
||||
<dimen name="activity_horizontal_margin">16dp</dimen>
|
||||
<dimen name="activity_vertical_margin">16dp</dimen>
|
||||
|
||||
<!-- Per the design guidelines, navigation drawers should be between 240dp and 320dp:
|
||||
https://developer.android.com/design/patterns/navigation-drawer.html -->
|
||||
<dimen name="navigation_drawer_width">240dp</dimen>
|
||||
</resources>
|
@@ -0,0 +1,17 @@
|
||||
<resources>
|
||||
<#if !isNewProject>
|
||||
<string name="title_${activityToLayout(activityClass)}">${escapeXmlString(activityTitle)}</string>
|
||||
</#if>
|
||||
|
||||
<string name="title_section1">Section 1</string>
|
||||
<string name="title_section2">Section 2</string>
|
||||
<string name="title_section3">Section 3</string>
|
||||
|
||||
<string name="navigation_drawer_open">Open navigation drawer</string>
|
||||
<string name="navigation_drawer_close">Close navigation drawer</string>
|
||||
|
||||
<string name="action_example">Example action</string>
|
||||
|
||||
<string name="action_settings">Settings</string>
|
||||
|
||||
</resources>
|
@@ -0,0 +1,84 @@
|
||||
package ${packageName};
|
||||
|
||||
import android.app.Activity;
|
||||
<#if appCompat>import android.support.v7.app.ActionBarActivity;</#if>
|
||||
import android.<#if appCompat>support.v7.</#if>app.ActionBar;
|
||||
import android.<#if appCompat>support.v4.</#if>app.Fragment;
|
||||
import android.<#if appCompat>support.v4.</#if>app.FragmentManager;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.support.v4.widget.DrawerLayout;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.TextView;
|
||||
<#if applicationPackage??>import ${applicationPackage}.R;</#if>
|
||||
|
||||
public class ${activityClass} extends ${(appCompat)?string('ActionBar','')}Activity
|
||||
implements NavigationDrawerFragment.NavigationDrawerCallbacks {
|
||||
|
||||
/**
|
||||
* Fragment managing the behaviors, interactions and presentation of the navigation drawer.
|
||||
*/
|
||||
private NavigationDrawerFragment mNavigationDrawerFragment;
|
||||
|
||||
/**
|
||||
* Used to store the last screen title. For use in {@link #restoreActionBar()}.
|
||||
*/
|
||||
private CharSequence mTitle;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.${layoutName});
|
||||
|
||||
mNavigationDrawerFragment = (NavigationDrawerFragment)
|
||||
get${Support}FragmentManager().findFragmentById(R.id.navigation_drawer);
|
||||
mTitle = getTitle();
|
||||
|
||||
// Set up the drawer.
|
||||
mNavigationDrawerFragment.setUp(
|
||||
R.id.navigation_drawer,
|
||||
(DrawerLayout) findViewById(R.id.drawer_layout));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNavigationDrawerItemSelected(int position) {
|
||||
// update the main content by replacing fragments
|
||||
FragmentManager fragmentManager = get${Support}FragmentManager();
|
||||
fragmentManager.beginTransaction()
|
||||
.replace(R.id.container, PlaceholderFragment.newInstance(position + 1))
|
||||
.commit();
|
||||
}
|
||||
|
||||
public void onSectionAttached(int number) {
|
||||
switch (number) {
|
||||
case 1:
|
||||
mTitle = getString(R.string.title_section1);
|
||||
break;
|
||||
case 2:
|
||||
mTitle = getString(R.string.title_section2);
|
||||
break;
|
||||
case 3:
|
||||
mTitle = getString(R.string.title_section3);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void restoreActionBar() {
|
||||
ActionBar actionBar = get${Support}ActionBar();
|
||||
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
|
||||
actionBar.setDisplayShowTitleEnabled(true);
|
||||
actionBar.setTitle(mTitle);
|
||||
}
|
||||
|
||||
<#include "include_options_menu.java.ftl">
|
||||
|
||||
<#include "include_fragment.java.ftl">
|
||||
|
||||
}
|
@@ -0,0 +1,282 @@
|
||||
package ${packageName};
|
||||
|
||||
<#if appCompat>import android.support.v7.app.ActionBarActivity;</#if>
|
||||
import android.app.Activity;
|
||||
import android.<#if appCompat>support.v7.</#if>app.ActionBar;
|
||||
import android.<#if appCompat>support.v4.</#if>app.Fragment;
|
||||
import android.support.v4.app.ActionBarDrawerToggle;
|
||||
import android.support.v4.view.GravityCompat;
|
||||
import android.support.v4.widget.DrawerLayout;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ListView;
|
||||
import android.widget.Toast;
|
||||
|
||||
/**
|
||||
* Fragment used for managing interactions for and presentation of a navigation drawer.
|
||||
* See the <a href="https://developer.android.com/design/patterns/navigation-drawer.html#Interaction">
|
||||
* design guidelines</a> for a complete explanation of the behaviors implemented here.
|
||||
*/
|
||||
public class NavigationDrawerFragment extends Fragment {
|
||||
|
||||
/**
|
||||
* Remember the position of the selected item.
|
||||
*/
|
||||
private static final String STATE_SELECTED_POSITION = "selected_navigation_drawer_position";
|
||||
|
||||
/**
|
||||
* Per the design guidelines, you should show the drawer on launch until the user manually
|
||||
* expands it. This shared preference tracks this.
|
||||
*/
|
||||
private static final String PREF_USER_LEARNED_DRAWER = "navigation_drawer_learned";
|
||||
|
||||
/**
|
||||
* A pointer to the current callbacks instance (the Activity).
|
||||
*/
|
||||
private NavigationDrawerCallbacks mCallbacks;
|
||||
|
||||
/**
|
||||
* Helper component that ties the action bar to the navigation drawer.
|
||||
*/
|
||||
private ActionBarDrawerToggle mDrawerToggle;
|
||||
|
||||
private DrawerLayout mDrawerLayout;
|
||||
private ListView mDrawerListView;
|
||||
private View mFragmentContainerView;
|
||||
|
||||
private int mCurrentSelectedPosition = 0;
|
||||
private boolean mFromSavedInstanceState;
|
||||
private boolean mUserLearnedDrawer;
|
||||
|
||||
public NavigationDrawerFragment() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// Read in the flag indicating whether or not the user has demonstrated awareness of the
|
||||
// drawer. See PREF_USER_LEARNED_DRAWER for details.
|
||||
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getActivity());
|
||||
mUserLearnedDrawer = sp.getBoolean(PREF_USER_LEARNED_DRAWER, false);
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
mCurrentSelectedPosition = savedInstanceState.getInt(STATE_SELECTED_POSITION);
|
||||
mFromSavedInstanceState = true;
|
||||
}
|
||||
|
||||
// Select either the default item (0) or the last selected item.
|
||||
selectItem(mCurrentSelectedPosition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated (Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
// Indicate that this fragment would like to influence the set of actions in the action bar.
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
mDrawerListView = (ListView) inflater.inflate(
|
||||
R.layout.${navigationDrawerLayout}, container, false);
|
||||
mDrawerListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
selectItem(position);
|
||||
}
|
||||
});
|
||||
mDrawerListView.setAdapter(new ArrayAdapter<String>(
|
||||
getActionBar().getThemedContext(),
|
||||
android.R.layout.simple_list_item_<#if minApiLevel gte 11>activated_</#if>1,
|
||||
android.R.id.text1,
|
||||
new String[]{
|
||||
getString(R.string.title_section1),
|
||||
getString(R.string.title_section2),
|
||||
getString(R.string.title_section3),
|
||||
}));
|
||||
mDrawerListView.setItemChecked(mCurrentSelectedPosition, true);
|
||||
return mDrawerListView;
|
||||
}
|
||||
|
||||
public boolean isDrawerOpen() {
|
||||
return mDrawerLayout != null && mDrawerLayout.isDrawerOpen(mFragmentContainerView);
|
||||
}
|
||||
|
||||
/**
|
||||
* Users of this fragment must call this method to set up the navigation drawer interactions.
|
||||
*
|
||||
* @param fragmentId The android:id of this fragment in its activity's layout.
|
||||
* @param drawerLayout The DrawerLayout containing this fragment's UI.
|
||||
*/
|
||||
public void setUp(int fragmentId, DrawerLayout drawerLayout) {
|
||||
mFragmentContainerView = getActivity().findViewById(fragmentId);
|
||||
mDrawerLayout = drawerLayout;
|
||||
|
||||
// set a custom shadow that overlays the main content when the drawer opens
|
||||
mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START);
|
||||
// set up the drawer's list view with items and click listener
|
||||
|
||||
ActionBar actionBar = getActionBar();
|
||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||
actionBar.setHomeButtonEnabled(true);
|
||||
|
||||
// ActionBarDrawerToggle ties together the the proper interactions
|
||||
// between the navigation drawer and the action bar app icon.
|
||||
mDrawerToggle = new ActionBarDrawerToggle(
|
||||
getActivity(), /* host Activity */
|
||||
mDrawerLayout, /* DrawerLayout object */
|
||||
R.drawable.ic_drawer, /* nav drawer image to replace 'Up' caret */
|
||||
R.string.navigation_drawer_open, /* "open drawer" description for accessibility */
|
||||
R.string.navigation_drawer_close /* "close drawer" description for accessibility */
|
||||
) {
|
||||
@Override
|
||||
public void onDrawerClosed(View drawerView) {
|
||||
super.onDrawerClosed(drawerView);
|
||||
if (!isAdded()) {
|
||||
return;
|
||||
}
|
||||
|
||||
getActivity().${appCompat?string('supportInvalidate','invalidate')}OptionsMenu(); // calls onPrepareOptionsMenu()
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDrawerOpened(View drawerView) {
|
||||
super.onDrawerOpened(drawerView);
|
||||
if (!isAdded()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mUserLearnedDrawer) {
|
||||
// The user manually opened the drawer; store this flag to prevent auto-showing
|
||||
// the navigation drawer automatically in the future.
|
||||
mUserLearnedDrawer = true;
|
||||
SharedPreferences sp = PreferenceManager
|
||||
.getDefaultSharedPreferences(getActivity());
|
||||
sp.edit().putBoolean(PREF_USER_LEARNED_DRAWER, true).${(minApiLevel gte 9)?string('apply','commit')}();
|
||||
}
|
||||
|
||||
getActivity().${appCompat?string('supportInvalidate','invalidate')}OptionsMenu(); // calls onPrepareOptionsMenu()
|
||||
}
|
||||
};
|
||||
|
||||
// If the user hasn't 'learned' about the drawer, open it to introduce them to the drawer,
|
||||
// per the navigation drawer design guidelines.
|
||||
if (!mUserLearnedDrawer && !mFromSavedInstanceState) {
|
||||
mDrawerLayout.openDrawer(mFragmentContainerView);
|
||||
}
|
||||
|
||||
// Defer code dependent on restoration of previous instance state.
|
||||
mDrawerLayout.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mDrawerToggle.syncState();
|
||||
}
|
||||
});
|
||||
|
||||
mDrawerLayout.setDrawerListener(mDrawerToggle);
|
||||
}
|
||||
|
||||
private void selectItem(int position) {
|
||||
mCurrentSelectedPosition = position;
|
||||
if (mDrawerListView != null) {
|
||||
mDrawerListView.setItemChecked(position, true);
|
||||
}
|
||||
if (mDrawerLayout != null) {
|
||||
mDrawerLayout.closeDrawer(mFragmentContainerView);
|
||||
}
|
||||
if (mCallbacks != null) {
|
||||
mCallbacks.onNavigationDrawerItemSelected(position);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
try {
|
||||
mCallbacks = (NavigationDrawerCallbacks) activity;
|
||||
} catch (ClassCastException e) {
|
||||
throw new ClassCastException("Activity must implement NavigationDrawerCallbacks.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetach() {
|
||||
super.onDetach();
|
||||
mCallbacks = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putInt(STATE_SELECTED_POSITION, mCurrentSelectedPosition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
// Forward the new configuration the drawer toggle component.
|
||||
mDrawerToggle.onConfigurationChanged(newConfig);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
// If the drawer is open, show the global app actions in the action bar. See also
|
||||
// showGlobalContextActionBar, which controls the top-left area of the action bar.
|
||||
if (mDrawerLayout != null && isDrawerOpen()) {
|
||||
inflater.inflate(R.menu.global, menu);
|
||||
showGlobalContextActionBar();
|
||||
}
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (mDrawerToggle.onOptionsItemSelected(item)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (item.getItemId() == R.id.action_example) {
|
||||
Toast.makeText(getActivity(), "Example action.", Toast.LENGTH_SHORT).show();
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Per the navigation drawer design guidelines, updates the action bar to show the global app
|
||||
* 'context', rather than just what's in the current screen.
|
||||
*/
|
||||
private void showGlobalContextActionBar() {
|
||||
ActionBar actionBar = getActionBar();
|
||||
actionBar.setDisplayShowTitleEnabled(true);
|
||||
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
|
||||
actionBar.setTitle(R.string.app_name);
|
||||
}
|
||||
|
||||
private ActionBar getActionBar() {
|
||||
return <#if appCompat>((ActionBarActivity) getActivity()).getSupportActionBar();<#else>getActivity().getActionBar();</#if>
|
||||
}
|
||||
|
||||
/**
|
||||
* Callbacks interface that all activities using this fragment must implement.
|
||||
*/
|
||||
public static interface NavigationDrawerCallbacks {
|
||||
/**
|
||||
* Called when an item in the navigation drawer is selected.
|
||||
*/
|
||||
void onNavigationDrawerItemSelected(int position);
|
||||
}
|
||||
}
|
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* A placeholder fragment containing a simple view.
|
||||
*/
|
||||
public static class PlaceholderFragment extends Fragment {
|
||||
/**
|
||||
* The fragment argument representing the section number for this
|
||||
* fragment.
|
||||
*/
|
||||
private static final String ARG_SECTION_NUMBER = "section_number";
|
||||
|
||||
/**
|
||||
* Returns a new instance of this fragment for the given section
|
||||
* number.
|
||||
*/
|
||||
public static PlaceholderFragment newInstance(int sectionNumber) {
|
||||
PlaceholderFragment fragment = new PlaceholderFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putInt(ARG_SECTION_NUMBER, sectionNumber);
|
||||
fragment.setArguments(args);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
public PlaceholderFragment() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
View rootView = inflater.inflate(R.layout.${fragmentLayoutName}, container, false);
|
||||
<#if hasSections?has_content>
|
||||
TextView textView = (TextView) rootView.findViewById(R.id.section_label);
|
||||
textView.setText(Integer.toString(getArguments().getInt(ARG_SECTION_NUMBER)));
|
||||
</#if>
|
||||
return rootView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
((${activityClass}) activity).onSectionAttached(
|
||||
getArguments().getInt(ARG_SECTION_NUMBER));
|
||||
}
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
if (!mNavigationDrawerFragment.isDrawerOpen()) {
|
||||
// Only show items in the action bar relevant to this screen
|
||||
// if the drawer is not showing. Otherwise, let the drawer
|
||||
// decide what to show in the action bar.
|
||||
getMenuInflater().inflate(R.menu.${menuName}, menu);
|
||||
restoreActionBar();
|
||||
return true;
|
||||
}
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
// Handle action bar item clicks here. The action bar will
|
||||
// automatically handle clicks on the Home/Up button, so long
|
||||
// as you specify a parent activity in AndroidManifest.xml.
|
||||
int id = item.getItemId();
|
||||
if (id == R.id.action_settings) {
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
@@ -0,0 +1,87 @@
|
||||
<?xml version="1.0"?>
|
||||
<template
|
||||
format="3"
|
||||
revision="4"
|
||||
name="Navigation Drawer Activity"
|
||||
minApi="7"
|
||||
minBuildApi="14"
|
||||
description="Creates a new Activity with a Navigation Drawer.">
|
||||
|
||||
<category value="Activity" />
|
||||
<formfactor value="Mobile" />
|
||||
|
||||
<parameter
|
||||
id="activityClass"
|
||||
name="Activity Name"
|
||||
type="string"
|
||||
constraints="class|unique|nonempty"
|
||||
suggest="${layoutToActivity(layoutName)}"
|
||||
default="MainActivity"
|
||||
help="The name of the activity class to create" />
|
||||
|
||||
<parameter
|
||||
id="layoutName"
|
||||
name="Layout Name"
|
||||
type="string"
|
||||
constraints="layout|unique|nonempty"
|
||||
suggest="${activityToLayout(activityClass)}"
|
||||
default="activity_main"
|
||||
help="The name of the layout to create for the activity" />
|
||||
|
||||
<parameter
|
||||
id="fragmentLayoutName"
|
||||
name="Fragment Layout Name"
|
||||
type="string"
|
||||
constraints="layout|unique|nonempty"
|
||||
suggest="fragment_${classToResource(activityClass)}"
|
||||
default="fragment_main"
|
||||
help="The name of the layout to create for the activity's content fragment"/>
|
||||
|
||||
<parameter
|
||||
id="activityTitle"
|
||||
name="Title"
|
||||
type="string"
|
||||
constraints="nonempty"
|
||||
default="MainActivity"
|
||||
suggest="${activityClass}"
|
||||
help="The name of the activity. For launcher activities, the application title." />
|
||||
|
||||
<parameter
|
||||
id="isLauncher"
|
||||
name="Launcher Activity"
|
||||
type="boolean"
|
||||
default="false"
|
||||
help="If true, this activity will have a CATEGORY_LAUNCHER intent filter, making it visible in the launcher" />
|
||||
|
||||
<parameter
|
||||
id="parentActivityClass"
|
||||
name="Hierarchical Parent"
|
||||
type="string"
|
||||
constraints="activity|exists|empty"
|
||||
default=""
|
||||
help="The hierarchical parent activity, used to provide a default implementation for the 'Up' button" />
|
||||
|
||||
<parameter
|
||||
id="packageName"
|
||||
name="Package name"
|
||||
type="string"
|
||||
constraints="package"
|
||||
default="com.mycompany.myapp" />
|
||||
|
||||
<parameter
|
||||
id="navigationDrawerLayout"
|
||||
name="Navigation Drawer Fragment Name"
|
||||
type="string"
|
||||
constraints="layout|unique"
|
||||
default="fragment_navigation_drawer"/>
|
||||
|
||||
<!-- 128x128 thumbnails relative to template.xml -->
|
||||
<thumbs>
|
||||
<!-- default thumbnail is required -->
|
||||
<thumb>template_blank_activity_drawer.png</thumb>
|
||||
</thumbs>
|
||||
|
||||
<globals file="globals.xml.ftl" />
|
||||
<execute file="recipe.xml.ftl" />
|
||||
|
||||
</template>
|