diff --git a/README.md b/README.md index f9c0a4c1..43305759 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ public class MyAdapter extends BaseAdapter implements StickyListHeadersAdapter { private String[] countries; private LayoutInflater inflater; - public TestBaseAdapter(Context context) { + public MyAdapter(Context context) { inflater = LayoutInflater.from(context); countries = context.getResources().getStringArray(R.array.countries); } @@ -148,8 +148,29 @@ public class MyAdapter extends BaseAdapter implements StickyListHeadersAdapter { That's it! Look through the API docs below to get know about things to customize and if you have any problems getting started please open an issue as it probably means the getting started guide need some improvement! +###Styling + +You can apply your own theme to `StickyListHeadersListView`s. Say you define a style called `Widget.MyApp.ListView` in values/styles.xml: +```xml + + + +``` + +You can then apply this style to all `StickyListHeadersListView`s by adding something like this to your theme (e.g. values/themes.xml): +```xml + + + +``` + ###Expandable support -Now,you can use `ExpandableStickyListHeadersListView` to expand/collapse subitems. +Now, you can use `ExpandableStickyListHeadersListView` to expand/collapse subitems. xml first ```xml + android:targetSdkVersion="22" /> - \ No newline at end of file + diff --git a/library/build.gradle b/library/build.gradle index c7bf0981..84032a22 100644 --- a/library/build.gradle +++ b/library/build.gradle @@ -1,8 +1,8 @@ -apply plugin: 'android-library' +apply plugin: 'com.android.library' android { - compileSdkVersion 19 - buildToolsVersion '19.1.0' + compileSdkVersion 22 + buildToolsVersion '22.0.1' sourceSets { main { @@ -13,4 +13,4 @@ android { } } -apply from: 'https://raw.github.com/shamanland/gradle-mvn-push/cc18d56549cdea03f744b6fff27911569394073e/gradle-mvn-push.gradle' +apply from: 'gradle-mvn-push.gradle' diff --git a/library/gradle-mvn-push.gradle b/library/gradle-mvn-push.gradle new file mode 100644 index 00000000..724e01c5 --- /dev/null +++ b/library/gradle-mvn-push.gradle @@ -0,0 +1,116 @@ +/* + * Copyright 2013 Chris Banes + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +apply plugin: 'maven' +apply plugin: 'signing' + +def isReleaseBuild() { + return VERSION_NAME.contains("SNAPSHOT") == false +} + +def getReleaseRepositoryUrl() { + return hasProperty('RELEASE_REPOSITORY_URL') ? RELEASE_REPOSITORY_URL + : "https://oss.sonatype.org/service/local/staging/deploy/maven2/" +} + +def getSnapshotRepositoryUrl() { + return hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL + : "https://oss.sonatype.org/content/repositories/snapshots/" +} + +def getRepositoryUsername() { + return hasProperty('NEXUS_USERNAME') ? NEXUS_USERNAME : "" +} + +def getRepositoryPassword() { + return hasProperty('NEXUS_PASSWORD') ? NEXUS_PASSWORD : "" +} + +afterEvaluate { project -> + uploadArchives { + repositories { + mavenDeployer { + beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } + + pom.groupId = GROUP + pom.artifactId = POM_ARTIFACT_ID + pom.version = VERSION_NAME + + repository(url: getReleaseRepositoryUrl()) { + authentication(userName: getRepositoryUsername(), password: getRepositoryPassword()) + } + snapshotRepository(url: getSnapshotRepositoryUrl()) { + authentication(userName: getRepositoryUsername(), password: getRepositoryPassword()) + } + + pom.project { + name POM_NAME + packaging POM_PACKAGING + description POM_DESCRIPTION + url POM_URL + + scm { + url POM_SCM_URL + connection POM_SCM_CONNECTION + developerConnection POM_SCM_DEV_CONNECTION + } + + licenses { + license { + name POM_LICENCE_NAME + url POM_LICENCE_URL + distribution POM_LICENCE_DIST + } + } + + developers { + developer { + id POM_DEVELOPER_ID + name POM_DEVELOPER_NAME + email POM_DEVELOPER_EMAIL + } + } + } + } + } + } + + signing { + required { isReleaseBuild() && gradle.taskGraph.hasTask("uploadArchives") } + sign configurations.archives + } + + task androidJavadocs(type: Javadoc) { + failOnError = false + source = android.sourceSets.main.java.source + classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) + } + + task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) { + classifier = 'javadoc' + from androidJavadocs.destinationDir + } + + task androidSourcesJar(type: Jar) { + classifier = 'sources' + from android.sourceSets.main.java.source + } + + artifacts { + archives androidSourcesJar + archives androidJavadocsJar + } +} diff --git a/library/res/values/attrs.xml b/library/res/values/attrs.xml index 210ebe72..fc811683 100644 --- a/library/res/values/attrs.xml +++ b/library/res/values/attrs.xml @@ -2,6 +2,8 @@ + + @@ -26,10 +28,11 @@ + - \ No newline at end of file + diff --git a/library/src/se/emilsjolander/stickylistheaders/AdapterWrapper.java b/library/src/se/emilsjolander/stickylistheaders/AdapterWrapper.java index ce2471a8..fb363a9d 100644 --- a/library/src/se/emilsjolander/stickylistheaders/AdapterWrapper.java +++ b/library/src/se/emilsjolander/stickylistheaders/AdapterWrapper.java @@ -31,7 +31,7 @@ interface OnHeaderClickListener { boolean onHeaderLongClick(View header, int itemPosition, long headerId); } - final StickyListHeadersAdapter mDelegate; + StickyListHeadersAdapter mDelegate; private final List mHeaderCache = new LinkedList(); private final Context mContext; private Drawable mDivider; diff --git a/library/src/se/emilsjolander/stickylistheaders/SectionIndexerAdapterWrapper.java b/library/src/se/emilsjolander/stickylistheaders/SectionIndexerAdapterWrapper.java index ff7e293a..3fe3d1b8 100644 --- a/library/src/se/emilsjolander/stickylistheaders/SectionIndexerAdapterWrapper.java +++ b/library/src/se/emilsjolander/stickylistheaders/SectionIndexerAdapterWrapper.java @@ -6,7 +6,7 @@ class SectionIndexerAdapterWrapper extends AdapterWrapper implements SectionIndexer { - final SectionIndexer mSectionIndexerDelegate; + SectionIndexer mSectionIndexerDelegate; SectionIndexerAdapterWrapper(Context context, StickyListHeadersAdapter delegate) { diff --git a/library/src/se/emilsjolander/stickylistheaders/StickyListHeadersListView.java b/library/src/se/emilsjolander/stickylistheaders/StickyListHeadersListView.java index 26452404..bdc2c633 100644 --- a/library/src/se/emilsjolander/stickylistheaders/StickyListHeadersListView.java +++ b/library/src/se/emilsjolander/stickylistheaders/StickyListHeadersListView.java @@ -14,6 +14,7 @@ import android.util.SparseBooleanArray; import android.view.MotionEvent; import android.view.View; +import android.view.ViewConfiguration; import android.view.ViewGroup; import android.widget.AbsListView; import android.widget.AbsListView.MultiChoiceModeListener; @@ -99,6 +100,11 @@ void onStickyHeaderChanged(StickyListHeadersListView l, View header, private int mPaddingRight = 0; private int mPaddingBottom = 0; + /* --- Touch handling --- */ + private float mDownY; + private boolean mHeaderOwnsTouch; + private float mTouchSlop; + /* --- Other --- */ private OnHeaderClickListener mOnHeaderClickListener; private OnStickyHeaderOffsetChangedListener mOnStickyHeaderOffsetChangedListener; @@ -112,13 +118,15 @@ public StickyListHeadersListView(Context context) { } public StickyListHeadersListView(Context context, AttributeSet attrs) { - this(context, attrs, 0); + this(context, attrs, R.attr.stickyListHeadersListViewStyle); } @TargetApi(Build.VERSION_CODES.HONEYCOMB) public StickyListHeadersListView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); + mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); + // Initialize the wrapped list mList = new WrapperViewList(context); @@ -129,7 +137,7 @@ public StickyListHeadersListView(Context context, AttributeSet attrs, int defSty mList.setDividerHeight(0); if (attrs != null) { - TypedArray a = context.getTheme().obtainStyledAttributes(attrs,R.styleable.StickyListHeadersListView, 0, 0); + TypedArray a = context.getTheme().obtainStyledAttributes(attrs,R.styleable.StickyListHeadersListView, defStyle, 0); try { // -- View attributes -- @@ -198,6 +206,8 @@ public StickyListHeadersListView(Context context, AttributeSet attrs, int defSty if (a.hasValue(R.styleable.StickyListHeadersListView_android_divider)) { mDivider = a.getDrawable(R.styleable.StickyListHeadersListView_android_divider); } + + mList.setStackFromBottom(a.getBoolean(R.styleable.StickyListHeadersListView_android_stackFromBottom, false)); mDividerHeight = a.getDimensionPixelSize(R.styleable.StickyListHeadersListView_android_dividerHeight, mDividerHeight); @@ -257,7 +267,7 @@ protected void onLayout(boolean changed, int left, int top, int right, int botto mList.layout(0, 0, mList.getMeasuredWidth(), getHeight()); if (mHeader != null) { MarginLayoutParams lp = (MarginLayoutParams) mHeader.getLayoutParams(); - int headerTop = lp.topMargin + stickyHeaderTop(); + int headerTop = lp.topMargin; mHeader.layout(mPaddingLeft, headerTop, mHeader.getMeasuredWidth() + mPaddingLeft, headerTop + mHeader.getMeasuredHeight()); } @@ -349,18 +359,17 @@ private void updateHeader(int headerPosition) { } } - int headerOffset = 0; + int headerOffset = stickyHeaderTop(); // Calculate new header offset // Skip looking at the first view. it never matters because it always // results in a headerOffset = 0 - int headerBottom = mHeader.getMeasuredHeight() + stickyHeaderTop(); for (int i = 0; i < mList.getChildCount(); i++) { final View child = mList.getChildAt(i); final boolean doesChildHaveHeader = child instanceof WrapperView && ((WrapperView) child).hasHeader(); final boolean isChildFooter = mList.containsFooterView(child); if (child.getTop() >= stickyHeaderTop() && (doesChildHaveHeader || isChildFooter)) { - headerOffset = Math.min(child.getTop() - headerBottom, 0); + headerOffset = Math.min(child.getTop() - mHeader.getMeasuredHeight(), headerOffset); break; } } @@ -406,12 +415,7 @@ public boolean onLongClick(View v) { // hides the headers in the list under the sticky header. // Makes sure the other ones are showing private void updateHeaderVisibilities() { - int top; - if (mHeader != null) { - top = mHeader.getMeasuredHeight() + (mHeaderOffset != null ? mHeaderOffset : 0) + mStickyHeaderTopOffset; - } else { - top = stickyHeaderTop(); - } + int top = stickyHeaderTop(); int childCount = mList.getChildCount(); for (int i = 0; i < childCount; i++) { @@ -460,6 +464,39 @@ private void setHeaderOffet(int offset) { } } + @Override + public boolean dispatchTouchEvent(MotionEvent ev) { + int action = ev.getAction() & MotionEvent.ACTION_MASK; + if (action == MotionEvent.ACTION_DOWN) { + mDownY = ev.getY(); + mHeaderOwnsTouch = mHeader != null && mDownY <= mHeader.getHeight() + mHeaderOffset; + } + + boolean handled; + if (mHeaderOwnsTouch) { + if (mHeader != null && Math.abs(mDownY - ev.getY()) <= mTouchSlop) { + handled = mHeader.dispatchTouchEvent(ev); + } else { + if (mHeader != null) { + MotionEvent cancelEvent = MotionEvent.obtain(ev); + cancelEvent.setAction(MotionEvent.ACTION_CANCEL); + mHeader.dispatchTouchEvent(cancelEvent); + cancelEvent.recycle(); + } + + MotionEvent downEvent = MotionEvent.obtain(ev.getDownTime(), ev.getEventTime(), ev.getAction(), ev.getX(), mDownY, ev.getMetaState()); + downEvent.setAction(MotionEvent.ACTION_DOWN); + handled = mList.dispatchTouchEvent(downEvent); + downEvent.recycle(); + mHeaderOwnsTouch = false; + } + } else { + handled = mList.dispatchTouchEvent(ev); + } + + return handled; + } + private class AdapterWrapperDataSetObserver extends DataSetObserver { @Override @@ -680,6 +717,12 @@ private boolean requireSdkVersion(int versionCode) { public void setAdapter(StickyListHeadersAdapter adapter) { if (adapter == null) { + if (mAdapter instanceof SectionIndexerAdapterWrapper) { + ((SectionIndexerAdapterWrapper) mAdapter).mSectionIndexerDelegate = null; + } + if (mAdapter != null) { + mAdapter.mDelegate = null; + } mList.setAdapter(null); clearHeader(); return; @@ -1104,5 +1147,12 @@ public void setTranscriptMode (int mode) { public void setBlockLayoutChildren(boolean blockLayoutChildren) { mList.setBlockLayoutChildren(blockLayoutChildren); } + + public void setStackFromBottom(boolean stackFromBottom) { + mList.setStackFromBottom(stackFromBottom); + } + public boolean isStackFromBottom() { + return mList.isStackFromBottom(); + } } diff --git a/sample/AndroidManifest.xml b/sample/AndroidManifest.xml index 54b2aa1b..18cffb1c 100644 --- a/sample/AndroidManifest.xml +++ b/sample/AndroidManifest.xml @@ -5,7 +5,7 @@ + android:targetSdkVersion="22" /> - \ No newline at end of file + diff --git a/sample/build.gradle b/sample/build.gradle index e5215fe8..0e179e4f 100644 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -1,19 +1,25 @@ -apply plugin: 'android' +apply plugin: 'com.android.application' repositories { mavenCentral() } + dependencies { compile project(':library') - compile 'com.android.support:appcompat-v7:19.0.+' - compile 'com.android.support:support-v4:19.1.+' + compile 'com.android.support:appcompat-v7:22.0.+' + compile 'com.android.support:support-v4:22.0.+' compile 'com.nineoldandroids:library:2.4.0+' - } android { - compileSdkVersion 19 - buildToolsVersion '19.1.0' + compileSdkVersion 22 + buildToolsVersion '22.0.1' + + defaultConfig { + applicationId 'se.emilsjolander.stickylistheaders' + minSdkVersion 7 + targetSdkVersion 22 + } sourceSets { main {