diff --git a/.gitignore b/.gitignore index ce27571..3c0fdad 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ #idea .idea/ +*.iml # Built application files *.apk diff --git a/README.md b/README.md index 24927cd..531031a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,66 @@ # WheelView-Android Selector with wheel view, applicable to selecting money or other short length values. + +Use with Gradle: +--- + +``` +dependencies { + compile 'com.lantouzi.wheelview:library:1.1.1' +} +``` + Screenshot of Demo: -![Demo](https://raw.githubusercontent.com/lantouzi/WheelView-Android/master/preview/demo.jpg) \ No newline at end of file +--- +![Demo](https://raw.githubusercontent.com/lantouzi/WheelView-Android/master/preview/demo.png) + +Usage +--- +### Style the view in xml: + +* **lwvHighlightColor** highlight color for selected item and the cursor. +* **lwvMarkColor** color of mark on normal status. +* **lwvMarkTextColor** color of mark text on normal status. +* **lwvIntervalFactor** factor for calculate interval using text width.(larger means sparser) +* **lwvMarkRatio** ratio that decides how short is the short mark than the long mark. +* **lwvCursorSize** size(width) of the cursor upside. +* **lwvMarkTextSize** text size of mark text on normal status. +* **lwvCenterMarkTextSize** text size of the center mark text (on selected status) +* **lwvAdditionalCenterMark** additional text used for unit of the center mark. + +### Listener + +``` +public interface OnWheelItemSelectedListener { + // Called each time when the center index changed. + void onWheelItemChanged(WheelView wheelView, int position); + + // Called only when the center index selected and wheel never moving to others. + void onWheelItemSelected(WheelView wheelView, int position); +} +``` + + +### Limit scope of selection. +*(Added in 1.1.1)* + +* **setMinSelectableIndex/setMaxSelectableIndex** limit min/max index whitch is selectable in code. + +Check out the demo project for more information. + +License +--- + + 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. + diff --git a/build.gradle b/build.gradle index be515a8..f3b8339 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,8 @@ buildscript { } dependencies { classpath 'com.android.tools.build:gradle:1.3.0' - + classpath 'com.github.dcendents:android-maven-plugin:1.2' + classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.0' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } diff --git a/demo/build.gradle b/demo/build.gradle index 270047a..306238e 100644 --- a/demo/build.gradle +++ b/demo/build.gradle @@ -8,8 +8,8 @@ android { applicationId "com.lantouzi.wheelview.demo" minSdkVersion 11 targetSdkVersion 23 - versionCode 1 - versionName "1.0" + versionCode 4 + versionName "1.1.0" } buildTypes { release { @@ -22,6 +22,5 @@ android { dependencies { compile project(path: ':library') compile fileTree(dir: 'libs', include: ['*.jar']) - testCompile 'junit:junit:4.12' compile 'com.android.support:appcompat-v7:23.1.0' } diff --git a/demo/demo.iml b/demo/demo.iml index d88a457..96de818 100644 --- a/demo/demo.iml +++ b/demo/demo.iml @@ -65,26 +65,19 @@ - - + - - - - - - - + diff --git a/demo/src/main/java/com/lantouzi/wheelview/demo/MainActivity.java b/demo/src/main/java/com/lantouzi/wheelview/demo/MainActivity.java index e867b41..b283375 100644 --- a/demo/src/main/java/com/lantouzi/wheelview/demo/MainActivity.java +++ b/demo/src/main/java/com/lantouzi/wheelview/demo/MainActivity.java @@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity { private WheelView mWheelView, mWheelView2, mWheelView3, mWheelView4, mWheelView5; - private TextView mSelectedTv; + private TextView mSelectedTv, mChangedTv; @Override protected void onCreate(Bundle savedInstanceState) { @@ -26,6 +26,7 @@ protected void onCreate(Bundle savedInstanceState) { mWheelView4 = (WheelView) findViewById(R.id.wheelview4); mWheelView5 = (WheelView) findViewById(R.id.wheelview5); mSelectedTv = (TextView) findViewById(R.id.selected_tv); + mChangedTv = (TextView) findViewById(R.id.changed_tv); final List items = new ArrayList<>(); for (int i = 1; i <= 40; i++) { @@ -70,12 +71,29 @@ protected void onCreate(Bundle savedInstanceState) { mWheelView3.setAdditionCenterMark("m"); mWheelView4.setItems(items); + mWheelView4.setEnabled(false); mWheelView5.setItems(items); + mWheelView5.setMinSelectableIndex(3); + mWheelView5.setMaxSelectableIndex(items.size() - 3); + + items.remove(items.size() - 1); + items.remove(items.size() - 2); + items.remove(items.size() - 3); + items.remove(items.size() - 4); + + mSelectedTv.setText(String.format("onWheelItemSelected:%1$s", "")); + mChangedTv.setText(String.format("onWheelItemChanged:%1$s", "")); + mWheelView5.setOnWheelItemSelectedListener(new WheelView.OnWheelItemSelectedListener() { @Override - public void onWheelItemSelected(int position) { - mSelectedTv.setText("选择:" + items.get(position) + "万"); + public void onWheelItemSelected(WheelView wheelView, int position) { + mSelectedTv.setText(String.format("onWheelItemSelected:%1$s", wheelView.getItems().get(position))); + } + + @Override + public void onWheelItemChanged(WheelView wheelView, int position) { + mChangedTv.setText(String.format("onWheelItemChanged:%1$s", wheelView.getItems().get(position))); } }); diff --git a/demo/src/main/res/layout/activity_main.xml b/demo/src/main/res/layout/activity_main.xml index 4ac7b45..015a79e 100644 --- a/demo/src/main/res/layout/activity_main.xml +++ b/demo/src/main/res/layout/activity_main.xml @@ -74,6 +74,13 @@ app:lwvMarkTextColor="#CACACA" app:lwvMarkTextSize="12sp"/> + + - + @@ -65,22 +65,13 @@ + - - - - - - - - - - diff --git a/library/src/main/java/com/lantouzi/wheelview/WheelView.java b/library/src/main/java/com/lantouzi/wheelview/WheelView.java index 0bfcd84..06f1ff0 100644 --- a/library/src/main/java/com/lantouzi/wheelview/WheelView.java +++ b/library/src/main/java/com/lantouzi/wheelview/WheelView.java @@ -7,6 +7,8 @@ import android.graphics.Path; import android.graphics.Rect; import android.graphics.RectF; +import android.os.Parcel; +import android.os.Parcelable; import android.support.v4.view.GestureDetectorCompat; import android.support.v4.view.ViewCompat; import android.text.TextPaint; @@ -20,6 +22,7 @@ import com.lantouzi.wheelview.library.R; +import java.util.ArrayList; import java.util.List; /** @@ -31,7 +34,7 @@ public class WheelView extends View implements GestureDetector.OnGestureListener private Paint mMarkPaint; private TextPaint mMarkTextPaint; - private int mCenterIndex; + private int mCenterIndex = -1; private int mHighlightColor, mMarkTextColor; private int mMarkColor, mFadeMarkColor; @@ -61,6 +64,9 @@ public class WheelView extends View implements GestureDetector.OnGestureListener private GestureDetectorCompat mGestureDetectorCompat; // scroll control args ---- end + private int mLastSelectedIndex = -1; + private int mMinSelectableIndex = Integer.MIN_VALUE; + private int mMaxSelectableIndex = Integer.MAX_VALUE; public WheelView(Context context) { super(context); @@ -90,17 +96,17 @@ protected void init(AttributeSet attrs) { mNormalTextSize = density * 18; mBottomSpace = density * 6; - TypedArray ta = attrs == null ? null : getContext().obtainStyledAttributes(attrs, R.styleable.WheelView); + TypedArray ta = attrs == null ? null : getContext().obtainStyledAttributes(attrs, R.styleable.lwvWheelView); if (ta != null) { - mHighlightColor = ta.getColor(R.styleable.WheelView_lwvHighlightColor, mHighlightColor); - mMarkTextColor = ta.getColor(R.styleable.WheelView_lwvMarkTextColor, mMarkTextColor); - mMarkColor = ta.getColor(R.styleable.WheelView_lwvMarkColor, mMarkColor); - mIntervalFactor = ta.getFloat(R.styleable.WheelView_lwvIntervalFactor, mIntervalFactor); - mMarkRatio = ta.getFloat(R.styleable.WheelView_lwvMarkRatio, mMarkRatio); - mAdditionCenterMark = ta.getString(R.styleable.WheelView_lwvAdditionalCenterMark); - mCenterTextSize = ta.getDimension(R.styleable.WheelView_lwvCenterMarkTextSize, mCenterTextSize); - mNormalTextSize = ta.getDimension(R.styleable.WheelView_lwvMarkTextSize, mNormalTextSize); - mCursorSize = ta.getDimension(R.styleable.WheelView_lwvCursorSize, mCursorSize); + mHighlightColor = ta.getColor(R.styleable.lwvWheelView_lwvHighlightColor, mHighlightColor); + mMarkTextColor = ta.getColor(R.styleable.lwvWheelView_lwvMarkTextColor, mMarkTextColor); + mMarkColor = ta.getColor(R.styleable.lwvWheelView_lwvMarkColor, mMarkColor); + mIntervalFactor = ta.getFloat(R.styleable.lwvWheelView_lwvIntervalFactor, mIntervalFactor); + mMarkRatio = ta.getFloat(R.styleable.lwvWheelView_lwvMarkRatio, mMarkRatio); + mAdditionCenterMark = ta.getString(R.styleable.lwvWheelView_lwvAdditionalCenterMark); + mCenterTextSize = ta.getDimension(R.styleable.lwvWheelView_lwvCenterMarkTextSize, mCenterTextSize); + mNormalTextSize = ta.getDimension(R.styleable.lwvWheelView_lwvMarkTextSize, mNormalTextSize); + mCursorSize = ta.getDimension(R.styleable.lwvWheelView_lwvCursorSize, mCursorSize); } mFadeMarkColor = mHighlightColor & 0xAAFFFFFF; mIntervalFactor = Math.max(1, mIntervalFactor); @@ -196,7 +202,11 @@ private int measureHeight(int heightMeasure) { } public void fling(int velocityX, int velocityY) { - mScroller.fling(getScrollX(), getScrollY(), velocityX, velocityY, (int) -mMaxOverScrollDistance, (int) (mContentRectF.width() - mMaxOverScrollDistance), 0, 0, (int) mMaxOverScrollDistance, 0); + mScroller.fling(getScrollX(), getScrollY(), + velocityX, velocityY, + (int) (-mMaxOverScrollDistance + mMinSelectableIndex * mIntervalDis), (int) (mContentRectF.width() - mMaxOverScrollDistance - (mMarkCount - 1 - mMaxSelectableIndex) * mIntervalDis), + 0, 0, + (int) mMaxOverScrollDistance, 0); ViewCompat.postInvalidateOnAnimation(this); } @@ -234,17 +244,17 @@ protected void onDraw(Canvas canvas) { start = Math.max(start, -mViewScopeSize * 2); end = Math.min(end, mMarkCount + mViewScopeSize * 2); - // 对两端的绘制范围进行扩展 - if (mCenterIndex == mMarkCount - 1) { + // extends both ends + if (mCenterIndex == mMaxSelectableIndex) { end += mViewScopeSize; - } else if (mCenterIndex == 0) { + } else if (mCenterIndex == mMinSelectableIndex) { start -= mViewScopeSize; } float x = start * mIntervalDis; - float markHeight = mHeight - mBottomSpace - mCenterTextSize - mTopSpace; - // 小刻度的Y方向缩小量 + float markHeight = mHeight - mBottomSpace - mCenterTextSize - mTopSpace; + // small scale Y offset float smallMarkShrinkY = markHeight * (1 - mMarkRatio) / 2f; smallMarkShrinkY = Math.min((markHeight - mMarkWidth) / 2f, smallMarkShrinkY); @@ -306,23 +316,13 @@ protected void onDraw(Canvas canvas) { @Override public boolean onTouchEvent(MotionEvent event) { - if (mItems == null || mItems.size() == 0) { + if (mItems == null || mItems.size() == 0 || !isEnabled()) { return false; } boolean ret = mGestureDetectorCompat.onTouchEvent(event); if (!mFling && MotionEvent.ACTION_UP == event.getAction()) { - if (getScrollX() < -mMaxOverScrollDistance) { - mScroller.startScroll(getScrollX(), 0, (int) -mMaxOverScrollDistance - getScrollX(), 0); - invalidate(); - ret = true; - } else if (getScrollX() > mContentRectF.width() - mMaxOverScrollDistance) { - mScroller.startScroll(getScrollX(), 0, (int) (mContentRectF.width() - mMaxOverScrollDistance) - getScrollX(), 0); - invalidate(); - ret = true; - } else { - autoSettle(); - ret = true; - } + autoSettle(); + ret = true; } return ret || super.onTouchEvent(event); } @@ -352,19 +352,40 @@ private void autoSettle() { int sx = getScrollX(); float dx = mCenterIndex * mIntervalDis - sx - mMaxOverScrollDistance; mScroller.startScroll(sx, 0, (int) dx, 0); - invalidate(); + postInvalidate(); + if (mLastSelectedIndex != mCenterIndex) { + mLastSelectedIndex = mCenterIndex; + if (null != mOnWheelItemSelectedListener) { + mOnWheelItemSelectedListener.onWheelItemSelected(this, mCenterIndex); + } + } + } + + /** + * limit center index in bounds. + * + * @param center + * @return + */ + private int safeCenter(int center) { + if (center < mMinSelectableIndex) { + center = mMinSelectableIndex; + } else if (center > mMaxSelectableIndex) { + center = mMaxSelectableIndex; + } + return center; } private void refreshCenter(int offsetX) { int offset = (int) (offsetX + mMaxOverScrollDistance); - mCenterIndex = Math.round(offset / mIntervalDis); - if (mCenterIndex < 0) { - mCenterIndex = 0; - } else if (mCenterIndex > mMarkCount - 1) { - mCenterIndex = mMarkCount - 1; + int tempIndex = Math.round(offset / mIntervalDis); + tempIndex = safeCenter(tempIndex); + if (mCenterIndex == tempIndex) { + return; } + mCenterIndex = tempIndex; if (null != mOnWheelItemSelectedListener) { - mOnWheelItemSelectedListener.onWheelItemSelected(mCenterIndex); + mOnWheelItemSelectedListener.onWheelItemChanged(this, mCenterIndex); } } @@ -393,13 +414,52 @@ public void smoothSelectIndex(int index) { invalidate(); } + public int getMinSelectableIndex() { + return mMinSelectableIndex; + } + + public void setMinSelectableIndex(int minSelectableIndex) { + if (minSelectableIndex > mMaxSelectableIndex) { + minSelectableIndex = mMaxSelectableIndex; + } + mMinSelectableIndex = minSelectableIndex; + int afterCenter = safeCenter(mCenterIndex); + if (afterCenter != mCenterIndex) { + selectIndex(afterCenter); + } + } + + public int getMaxSelectableIndex() { + return mMaxSelectableIndex; + } + + public void setMaxSelectableIndex(int maxSelectableIndex) { + if (maxSelectableIndex < mMinSelectableIndex) { + maxSelectableIndex = mMinSelectableIndex; + } + mMaxSelectableIndex = maxSelectableIndex; + int afterCenter = safeCenter(mCenterIndex); + if (afterCenter != mCenterIndex) { + selectIndex(afterCenter); + } + } + public List getItems() { return mItems; } public void setItems(List items) { - mItems = items; + if (mItems == null) { + mItems = new ArrayList<>(); + } else { + mItems.clear(); + } + mItems.addAll(items); mMarkCount = null == mItems ? 0 : mItems.size(); + if (mMarkCount > 0) { + mMinSelectableIndex = Math.max(mMinSelectableIndex, 0); + mMaxSelectableIndex = Math.min(mMaxSelectableIndex, mMarkCount - 1); + } mCenterIndex = Math.min(mCenterIndex, mMarkCount); calcIntervalDis(); invalidate(); @@ -440,19 +500,20 @@ public boolean onSingleTapUp(MotionEvent e) { @Override public void onLongPress(MotionEvent e) { + } @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { float dis = distanceX; float scrollX = getScrollX(); - if (scrollX < 2 * -mMaxOverScrollDistance) { + if (scrollX < mMinSelectableIndex * mIntervalDis - 2 * mMaxOverScrollDistance) { dis = 0; - } else if (scrollX < -mMaxOverScrollDistance) { + } else if (scrollX < mMinSelectableIndex * mIntervalDis - mMaxOverScrollDistance) { dis = distanceX / 4.f; - } else if (scrollX > mContentRectF.width()) { + } else if (scrollX > mContentRectF.width() - (mMarkCount - mMaxSelectableIndex - 1) * mIntervalDis) { dis = 0; - } else if (scrollX > mContentRectF.width() - mMaxOverScrollDistance) { + } else if (scrollX > mContentRectF.width() - (mMarkCount - mMaxSelectableIndex - 1) * mIntervalDis - mMaxOverScrollDistance) { dis = distanceX / 4.f; } scrollBy((int) dis, 0); @@ -463,7 +524,7 @@ public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float d @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { float scrollX = getScrollX(); - if (scrollX < -mMaxOverScrollDistance || scrollX > mContentRectF.width() - mMaxOverScrollDistance) { + if (scrollX < -mMaxOverScrollDistance + mMinSelectableIndex * mIntervalDis || scrollX > mContentRectF.width() - mMaxOverScrollDistance - (mMarkCount - 1 - mMaxSelectableIndex) * mIntervalDis) { return false; } else { mFling = true; @@ -472,7 +533,71 @@ public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float ve } } + @Override + public Parcelable onSaveInstanceState() { + Parcelable superState = super.onSaveInstanceState(); + SavedState ss = new SavedState(superState); + ss.index = getSelectedPosition(); + ss.min = mMinSelectableIndex; + ss.max = mMaxSelectableIndex; + return ss; + } + + @Override + public void onRestoreInstanceState(Parcelable state) { + SavedState ss = (SavedState) state; + super.onRestoreInstanceState(ss.getSuperState()); + mMinSelectableIndex = ss.min; + mMaxSelectableIndex = ss.max; + selectIndex(ss.index); + requestLayout(); + } + public interface OnWheelItemSelectedListener { - void onWheelItemSelected(int position); + void onWheelItemChanged(WheelView wheelView, int position); + + void onWheelItemSelected(WheelView wheelView, int position); + } + + static class SavedState extends BaseSavedState { + public static final Parcelable.Creator CREATOR + = new Parcelable.Creator() { + public SavedState createFromParcel(Parcel in) { + return new SavedState(in); + } + + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; + int index; + int min; + int max; + + SavedState(Parcelable superState) { + super(superState); + } + + private SavedState(Parcel in) { + super(in); + index = in.readInt(); + min = in.readInt(); + max = in.readInt(); + } + + @Override + public void writeToParcel(Parcel out, int flags) { + super.writeToParcel(out, flags); + out.writeInt(index); + out.writeInt(min); + out.writeInt(max); + } + + @Override + public String toString() { + return "WheelView.SavedState{" + + Integer.toHexString(System.identityHashCode(this)) + + " index=" + index + " min=" + min + " max=" + max + "}"; + } } } diff --git a/library/src/main/res/values/attrs.xml b/library/src/main/res/values/attrs.xml index e1b29d7..dd5802f 100644 --- a/library/src/main/res/values/attrs.xml +++ b/library/src/main/res/values/attrs.xml @@ -1,6 +1,6 @@ - + @@ -9,7 +9,6 @@ - \ No newline at end of file diff --git a/preview/demo.jpg b/preview/demo.jpg deleted file mode 100644 index d1da9aa..0000000 Binary files a/preview/demo.jpg and /dev/null differ diff --git a/preview/demo.png b/preview/demo.png new file mode 100644 index 0000000..9585540 Binary files /dev/null and b/preview/demo.png differ