diff --git a/app/build.gradle b/app/build.gradle index 4a5efcf..7fc934c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -97,4 +97,6 @@ dependencies { compile 'me.leolin:ShortcutBadger:1.1.19@aar' annotationProcessor 'com.google.dagger:dagger-compiler:2.12' compile files('libs/processor.jar') + + compile 'com.contrarywind:Android-PickerView:4.1.3' } \ No newline at end of file diff --git a/app/src/main/java/com/shunzhi/parent/contract/apply/ApplySigninContract.java b/app/src/main/java/com/shunzhi/parent/contract/apply/ApplySigninContract.java new file mode 100644 index 0000000..29c85bd --- /dev/null +++ b/app/src/main/java/com/shunzhi/parent/contract/apply/ApplySigninContract.java @@ -0,0 +1,37 @@ +package com.shunzhi.parent.contract.apply; + +import android.widget.LinearLayout; + +import com.google.gson.JsonObject; +import com.share.mvpsdk.base.BasePresenter; +import com.share.mvpsdk.base.IBaseFragment; +import com.share.mvpsdk.base.IBaseModel; +import com.shunzhi.parent.bean.ToolBean; + +import java.util.List; + +import io.reactivex.Observable; + +/** + * Created by ToaHanDong on 2018/3/14. + */ + +public interface ApplySigninContract { + + abstract class ApplySigninPresenter extends BasePresenter{ + public abstract void getTools(LinearLayout linearLayout,String areaName); + } + + + interface IApplySigninModel extends IBaseModel{ + Observable getTools(String areaName); + } + + + interface IApplySigninView extends IBaseFragment{ + + void showTools(List toolBeanList); + + } + +} diff --git a/app/src/main/java/com/shunzhi/parent/model/apply/ApplySigninModel.java b/app/src/main/java/com/shunzhi/parent/model/apply/ApplySigninModel.java new file mode 100644 index 0000000..503f890 --- /dev/null +++ b/app/src/main/java/com/shunzhi/parent/model/apply/ApplySigninModel.java @@ -0,0 +1,23 @@ +package com.shunzhi.parent.model.apply; + +import com.google.gson.JsonObject; +import com.share.mvpsdk.base.BaseModel; +import com.shunzhi.parent.contract.apply.ApplySigninContract; +import com.shunzhi.parent.contract.ceping.CepingContract; + +import io.reactivex.Observable; + +/** + * Created by Administrator on 2018/4/17 0017. + */ + +public class ApplySigninModel extends BaseModel implements ApplySigninContract.IApplySigninModel{ + @Override + public Observable getTools(String areaName) { + return null; + } + + public static ApplySigninContract.IApplySigninModel newInstance() { + return new ApplySigninModel(); + } +} diff --git a/app/src/main/java/com/shunzhi/parent/presenter/apply/ApplySigninPresenter.java b/app/src/main/java/com/shunzhi/parent/presenter/apply/ApplySigninPresenter.java new file mode 100644 index 0000000..a0b17e0 --- /dev/null +++ b/app/src/main/java/com/shunzhi/parent/presenter/apply/ApplySigninPresenter.java @@ -0,0 +1,27 @@ +package com.shunzhi.parent.presenter.apply; + +import android.widget.LinearLayout; + +import com.shunzhi.parent.contract.apply.ApplySigninContract; +import com.shunzhi.parent.model.apply.ApplySigninModel; + +/** + * Created by Administrator on 2018/4/17 0017. + */ + +public class ApplySigninPresenter extends ApplySigninContract.ApplySigninPresenter{ + @Override + public ApplySigninContract.IApplySigninModel getModel() { + return ApplySigninModel.newInstance(); + } + + @Override + public void onStart() { + + } + + @Override + public void getTools(LinearLayout linearLayout, String areaName) { + + } +} diff --git a/app/src/main/java/com/shunzhi/parent/ui/activity/apply/ApplySigninActivity.java b/app/src/main/java/com/shunzhi/parent/ui/activity/apply/ApplySigninActivity.java index c357bc6..7b1ff6e 100644 --- a/app/src/main/java/com/shunzhi/parent/ui/activity/apply/ApplySigninActivity.java +++ b/app/src/main/java/com/shunzhi/parent/ui/activity/apply/ApplySigninActivity.java @@ -10,12 +10,19 @@ import android.view.View; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.TextView; +import android.widget.Toast; +import com.bigkoo.pickerview.builder.TimePickerBuilder; +import com.bigkoo.pickerview.listener.CustomListener; +import com.bigkoo.pickerview.listener.OnTimeSelectListener; import com.share.mvpsdk.base.BasePresenter; import com.share.mvpsdk.base.activity.BaseMVPCompatActivity; import com.shunzhi.parent.R; import com.shunzhi.parent.ui.fragment.apply.ApplySigninFragment; +import java.util.Calendar; +import java.util.Date; + /** * Created by wwx on 2018/4/10 0010. * @@ -69,4 +76,5 @@ public class ApplySigninActivity extends BaseMVPCompatActivity implements View.O public BasePresenter initPresenter() { return null; } + } diff --git a/app/src/main/java/com/shunzhi/parent/ui/fragment/ConsultFragment.java b/app/src/main/java/com/shunzhi/parent/ui/fragment/ConsultFragment.java index 264d4dc..125d0dd 100644 --- a/app/src/main/java/com/shunzhi/parent/ui/fragment/ConsultFragment.java +++ b/app/src/main/java/com/shunzhi/parent/ui/fragment/ConsultFragment.java @@ -1,20 +1,30 @@ package com.shunzhi.parent.ui.fragment; +import android.annotation.TargetApi; +import android.app.DatePickerDialog; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.os.Build; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.annotation.RequiresApi; import android.support.v4.widget.NestedScrollView; import android.text.TextUtils; +import android.util.Log; import android.view.View; import android.widget.EditText; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; +import android.widget.Toast; +import com.bigkoo.pickerview.builder.TimePickerBuilder; +import com.bigkoo.pickerview.listener.CustomListener; +import com.bigkoo.pickerview.listener.OnTimeSelectListener; +import com.bigkoo.pickerview.view.TimePickerView; import com.bumptech.glide.Glide; import com.jcodecraeer.xrecyclerview.XRecyclerView; import com.share.mvpsdk.base.BasePresenter; @@ -35,7 +45,10 @@ import com.shunzhi.parent.util.GlideUtils; import com.shunzhi.parent.views.TextAndImgShowView; import com.stx.xhb.xbanner.XBanner; +import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; import java.util.List; import cn.jzvd.JZVideoPlayerStandard; @@ -55,7 +68,7 @@ public class ConsultFragment extends BaseMVPCompatFragment imgesUrl = new ArrayList<>(); - List imgWebUrl=new ArrayList<>();//跳转的连接 + List imgWebUrl = new ArrayList<>();//跳转的连接 List describeList = new ArrayList<>(); List guanggaoList = new ArrayList<>(); List contextList = new ArrayList<>(); @@ -64,7 +77,7 @@ public class ConsultFragment extends BaseMVPCompatFragment guangGaoBeanList) { describeList.clear(); imgesUrl.clear(); - guanggaoList=guangGaoBeanList; + guanggaoList = guangGaoBeanList; for (int i = 0; i < guangGaoBeanList.size(); i++) { imgesUrl.add(AppConfig.BASE_URL_FILE + guangGaoBeanList.get(i).fileSrc); describeList.add(guangGaoBeanList.get(i).describe); @@ -283,7 +299,7 @@ public class ConsultFragment extends BaseMVPCompatFragment implements View.OnClickListener{ private RecyclerView recycle_attendance; private AttendanceAdapter attendanceAdapter; List list=new ArrayList<>(); private TextView tv_tips,tv_kaoqin_num,tv_kaoqin_user,tv_kaoqin_date; private LinearLayout layout_tv; + private ImageView iv_calendar; + private TimePickerView pvCustomLunar; public BasePresenter initPresenter() { - return null; + return new ApplySigninPresenter(); } @Override @@ -47,6 +68,10 @@ public class ApplySigninFragment extends BaseMVPCompatFragment { tv_kaoqin_user = view.findViewById(R.id.tv_kaoqin_user); tv_kaoqin_date = view.findViewById(R.id.tv_kaoqin_date); tv_kaoqin_num = view.findViewById(R.id.tv_kaoqin_num); + iv_calendar = view.findViewById(R.id.iv_calendar); + + iv_calendar .setOnClickListener(this); + recycle_attendance = view.findViewById(R.id.recycle_attendance); recycle_attendance.setLayoutManager(new LinearLayoutManager(getActivity())); attendanceAdapter = new AttendanceAdapter(getActivity()); @@ -62,5 +87,63 @@ public class ApplySigninFragment extends BaseMVPCompatFragment { attendanceAdapter.addAll(list); recycle_attendance.setAdapter(attendanceAdapter); } + initLunarPicker(); + } + + @RequiresApi(api = Build.VERSION_CODES.N) + @Override + public void onClick(View v) { + switch (v.getId()){ + case R.id.iv_calendar: + pvCustomLunar.show(); + break; + } } + + private void initLunarPicker() { + Calendar selectedDate = Calendar.getInstance();//系统当前时间 + Calendar startDate = Calendar.getInstance(); + startDate.set(1900, 1, 1); + Calendar endDate = Calendar.getInstance(); + endDate.set(2099, 12, 31); + //AppContext.getInstance().startLocation(); + //时间选择器 + pvCustomLunar = new TimePickerBuilder(getActivity(), new OnTimeSelectListener() { + @Override + public void onTimeSelect(Date date, View v) { + ToastUtils.showToast(getTime(date)); + tv_kaoqin_date.setText(getTime(date)); + } + }) + .setDate(selectedDate) + .setRangDate(startDate, endDate) + .setLayoutRes(R.layout.pickerview_custom_lunar, new CustomListener() { + @Override + public void customLayout(View v) { + final TextView tvSubmit = (TextView) v.findViewById(R.id.tv_finish); + ImageView ivCancel = (ImageView) v.findViewById(R.id.iv_cancel); + tvSubmit.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + pvCustomLunar.returnData(); + pvCustomLunar.dismiss(); + } + }); + ivCancel.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + pvCustomLunar.dismiss(); + } + }); + } + }).build(); + } + + + private String getTime(Date date) {//可根据需要自行截取数据显示 + Log.d("getTime()", "choice date millis: " + date.getTime()); + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd"); + return format.format(date); + } + } diff --git a/app/src/main/java/com/shunzhi/parent/ui/fragment/report/ChengZhangFragment.java b/app/src/main/java/com/shunzhi/parent/ui/fragment/report/ChengZhangFragment.java index 28c4a66..2f1f916 100644 --- a/app/src/main/java/com/shunzhi/parent/ui/fragment/report/ChengZhangFragment.java +++ b/app/src/main/java/com/shunzhi/parent/ui/fragment/report/ChengZhangFragment.java @@ -79,7 +79,6 @@ public class ChengZhangFragment extends BaseMVPCompatFragment - - @@ -66,8 +65,8 @@ android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" + android:gravity="center" android:orientation="horizontal"> - - - - + android:orientation="horizontal"> + + + - - + android:divider="@color/divider_gray"> + android:layout_marginLeft="@dimen/dp_5" + android:layout_weight="1" + android:orientation="vertical"> + + android:background="@color/line_color" /> + - + android:layout_weight="9" + android:orientation="vertical"> + + + + + + + + + + + + + + + + - + android:layout_height="0dp" + android:background="@drawable/photo" /> diff --git a/app/src/main/res/layout/pickerview_custom_lunar.xml b/app/src/main/res/layout/pickerview_custom_lunar.xml new file mode 100644 index 0000000..adf93fc --- /dev/null +++ b/app/src/main/res/layout/pickerview_custom_lunar.xml @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/wheelview/.gitignore b/wheelview/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/wheelview/.gitignore @@ -0,0 +1 @@ +/build diff --git a/wheelview/build.gradle b/wheelview/build.gradle new file mode 100644 index 0000000..3e29450 --- /dev/null +++ b/wheelview/build.gradle @@ -0,0 +1,49 @@ +apply plugin: 'com.android.library' +apply plugin: 'com.github.dcendents.android-maven' +apply plugin: 'com.novoda.bintray-release'//添加插件 + + + +android { + compileSdkVersion 26 + buildToolsVersion "26.0.2" + + defaultConfig { + minSdkVersion 14 + targetSdkVersion 26 + versionCode 28 + versionName "4.0.5" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + lintOptions { + abortOnError false + } +} + +allprojects { + tasks.withType(Javadoc) {//兼容中文字符 + options{ + encoding "UTF-8" + charSet 'UTF-8' + links "http://docs.oracle.com/javase/7/docs/api" + } + } +} +publish { + userOrg = 'contrarywind'//bintray.com 用户名/组织名 user/org name + groupId = 'com.contrarywind'//JCenter上显示的路径 path + artifactId = 'wheelview'//项目名称 project name + publishVersion = '4.0.5'//版本号 version code + desc = 'this is a wheelview for android'//项目描述 description + website = 'https://github.com/Bigkoo/Android-PickerView' //项目网址链接 link +} + +dependencies { + compile fileTree(include: ['*.jar'], dir: 'libs') + +} \ No newline at end of file diff --git a/wheelview/proguard-rules.pro b/wheelview/proguard-rules.pro new file mode 100644 index 0000000..cbf2501 --- /dev/null +++ b/wheelview/proguard-rules.pro @@ -0,0 +1,25 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in C:\Users\song\AppData\Local\Android\sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/wheelview/src/androidTest/java/test/wheelview/ExampleInstrumentedTest.java b/wheelview/src/androidTest/java/test/wheelview/ExampleInstrumentedTest.java new file mode 100644 index 0000000..55262b5 --- /dev/null +++ b/wheelview/src/androidTest/java/test/wheelview/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package test.wheelview; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumentation test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() throws Exception { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getTargetContext(); + + assertEquals("test.wheelview.test", appContext.getPackageName()); + } +} diff --git a/wheelview/src/main/AndroidManifest.xml b/wheelview/src/main/AndroidManifest.xml new file mode 100644 index 0000000..1d6d9cc --- /dev/null +++ b/wheelview/src/main/AndroidManifest.xml @@ -0,0 +1,10 @@ + + + + + + + diff --git a/wheelview/src/main/java/com/contrarywind/adapter/WheelAdapter.java b/wheelview/src/main/java/com/contrarywind/adapter/WheelAdapter.java new file mode 100644 index 0000000..9f502f8 --- /dev/null +++ b/wheelview/src/main/java/com/contrarywind/adapter/WheelAdapter.java @@ -0,0 +1,25 @@ +package com.contrarywind.adapter; + + +public interface WheelAdapter { + /** + * Gets items count + * @return the count of wheel items + */ + int getItemsCount(); + + /** + * Gets a wheel item by index. + * @param index the item index + * @return the wheel item text or null + */ + T getItem(int index); + + /** + * Gets maximum item length. It is used to determine the wheel width. + * If -1 is returned there will be used the default wheel width. + * @param o the item object + * @return the maximum item length or -1 + */ + int indexOf(T o); +} diff --git a/wheelview/src/main/java/com/contrarywind/interfaces/IPickerViewData.java b/wheelview/src/main/java/com/contrarywind/interfaces/IPickerViewData.java new file mode 100644 index 0000000..2700146 --- /dev/null +++ b/wheelview/src/main/java/com/contrarywind/interfaces/IPickerViewData.java @@ -0,0 +1,8 @@ +package com.contrarywind.interfaces; + +/** + * Created by Sai on 2016/7/13. + */ +public interface IPickerViewData { + String getPickerViewText(); +} diff --git a/wheelview/src/main/java/com/contrarywind/listener/LoopViewGestureListener.java b/wheelview/src/main/java/com/contrarywind/listener/LoopViewGestureListener.java new file mode 100644 index 0000000..c2aa8e7 --- /dev/null +++ b/wheelview/src/main/java/com/contrarywind/listener/LoopViewGestureListener.java @@ -0,0 +1,25 @@ +package com.contrarywind.listener; + +import android.view.MotionEvent; + +import com.contrarywind.view.WheelView; + + +/** + * 手势监听 + */ +public final class LoopViewGestureListener extends android.view.GestureDetector.SimpleOnGestureListener { + + private final WheelView wheelView; + + + public LoopViewGestureListener(WheelView wheelView) { + this.wheelView = wheelView; + } + + @Override + public final boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { + wheelView.scrollBy(velocityY); + return true; + } +} diff --git a/wheelview/src/main/java/com/contrarywind/listener/OnItemSelectedListener.java b/wheelview/src/main/java/com/contrarywind/listener/OnItemSelectedListener.java new file mode 100644 index 0000000..db4023a --- /dev/null +++ b/wheelview/src/main/java/com/contrarywind/listener/OnItemSelectedListener.java @@ -0,0 +1,6 @@ +package com.contrarywind.listener; + + +public interface OnItemSelectedListener { + void onItemSelected(int index); +} diff --git a/wheelview/src/main/java/com/contrarywind/timer/InertiaTimerTask.java b/wheelview/src/main/java/com/contrarywind/timer/InertiaTimerTask.java new file mode 100644 index 0000000..2c29cf9 --- /dev/null +++ b/wheelview/src/main/java/com/contrarywind/timer/InertiaTimerTask.java @@ -0,0 +1,79 @@ +package com.contrarywind.timer; + +import com.contrarywind.view.WheelView; + +import java.util.TimerTask; + +/** + * 滚动惯性的实现 + * + * @author 小嵩 + * date: 2017-12-23 23:20:44 + */ +public final class InertiaTimerTask extends TimerTask { + + private float mCurrentVelocityY; //当前滑动速度 + private final float mFirstVelocityY;//手指离开屏幕时的初始速度 + private final WheelView mWheelView; + + /** + * @param wheelView 滚轮对象 + * @param velocityY Y轴滑行速度 + */ + public InertiaTimerTask(WheelView wheelView, float velocityY) { + super(); + this.mWheelView = wheelView; + this.mFirstVelocityY = velocityY; + mCurrentVelocityY = Integer.MAX_VALUE; + } + + @Override + public final void run() { + + //防止闪动,对速度做一个限制。 + if (mCurrentVelocityY == Integer.MAX_VALUE) { + if (Math.abs(mFirstVelocityY) > 2000F) { + mCurrentVelocityY = mFirstVelocityY > 0 ? 2000F : -2000F; + } else { + mCurrentVelocityY = mFirstVelocityY; + } + } + + //发送handler消息 处理平顺停止滚动逻辑 + if (Math.abs(mCurrentVelocityY) >= 0.0F && Math.abs(mCurrentVelocityY) <= 20F) { + mWheelView.cancelFuture(); + mWheelView.getHandler().sendEmptyMessage(MessageHandler.WHAT_SMOOTH_SCROLL); + return; + } + + int dy = (int) (mCurrentVelocityY / 100F); + mWheelView.setTotalScrollY(mWheelView.getTotalScrollY() - dy); + if (!mWheelView.isLoop()) { + float itemHeight = mWheelView.getItemHeight(); + float top = (-mWheelView.getInitPosition()) * itemHeight; + float bottom = (mWheelView.getItemsCount() - 1 - mWheelView.getInitPosition()) * itemHeight; + if (mWheelView.getTotalScrollY() - itemHeight * 0.25 < top) { + top = mWheelView.getTotalScrollY() + dy; + } else if (mWheelView.getTotalScrollY() + itemHeight * 0.25 > bottom) { + bottom = mWheelView.getTotalScrollY() + dy; + } + + if (mWheelView.getTotalScrollY() <= top) { + mCurrentVelocityY = 40F; + mWheelView.setTotalScrollY((int) top); + } else if (mWheelView.getTotalScrollY() >= bottom) { + mWheelView.setTotalScrollY((int) bottom); + mCurrentVelocityY = -40F; + } + } + + if (mCurrentVelocityY < 0.0F) { + mCurrentVelocityY = mCurrentVelocityY + 20F; + } else { + mCurrentVelocityY = mCurrentVelocityY - 20F; + } + + //刷新UI + mWheelView.getHandler().sendEmptyMessage(MessageHandler.WHAT_INVALIDATE_LOOP_VIEW); + } +} diff --git a/wheelview/src/main/java/com/contrarywind/timer/MessageHandler.java b/wheelview/src/main/java/com/contrarywind/timer/MessageHandler.java new file mode 100644 index 0000000..cdc1bf3 --- /dev/null +++ b/wheelview/src/main/java/com/contrarywind/timer/MessageHandler.java @@ -0,0 +1,42 @@ +package com.contrarywind.timer; + +import android.os.Handler; +import android.os.Message; + +import com.contrarywind.view.WheelView; + +/** + * Handler 消息类 + * + * @author 小嵩 + * date: 2017-12-23 23:20:44 + */ +public final class MessageHandler extends Handler { + public static final int WHAT_INVALIDATE_LOOP_VIEW = 1000; + public static final int WHAT_SMOOTH_SCROLL = 2000; + public static final int WHAT_ITEM_SELECTED = 3000; + + private final WheelView wheelView; + + public MessageHandler(WheelView wheelView) { + this.wheelView = wheelView; + } + + @Override + public final void handleMessage(Message msg) { + switch (msg.what) { + case WHAT_INVALIDATE_LOOP_VIEW: + wheelView.invalidate(); + break; + + case WHAT_SMOOTH_SCROLL: + wheelView.smoothScroll(WheelView.ACTION.FLING); + break; + + case WHAT_ITEM_SELECTED: + wheelView.onItemSelected(); + break; + } + } + +} diff --git a/wheelview/src/main/java/com/contrarywind/timer/SmoothScrollTimerTask.java b/wheelview/src/main/java/com/contrarywind/timer/SmoothScrollTimerTask.java new file mode 100644 index 0000000..7125775 --- /dev/null +++ b/wheelview/src/main/java/com/contrarywind/timer/SmoothScrollTimerTask.java @@ -0,0 +1,64 @@ +package com.contrarywind.timer; + +import com.contrarywind.view.WheelView; + +import java.util.TimerTask; + +/** + * 平滑滚动的实现 + * + * @author 小嵩 + */ +public final class SmoothScrollTimerTask extends TimerTask { + + private int realTotalOffset; + private int realOffset; + private int offset; + private final WheelView wheelView; + + public SmoothScrollTimerTask(WheelView wheelView, int offset) { + this.wheelView = wheelView; + this.offset = offset; + realTotalOffset = Integer.MAX_VALUE; + realOffset = 0; + } + + @Override + public final void run() { + if (realTotalOffset == Integer.MAX_VALUE) { + realTotalOffset = offset; + } + //把要滚动的范围细分成10小份,按10小份单位来重绘 + realOffset = (int) ((float) realTotalOffset * 0.1F); + + if (realOffset == 0) { + if (realTotalOffset < 0) { + realOffset = -1; + } else { + realOffset = 1; + } + } + + if (Math.abs(realTotalOffset) <= 1) { + wheelView.cancelFuture(); + wheelView.getHandler().sendEmptyMessage(MessageHandler.WHAT_ITEM_SELECTED); + } else { + wheelView.setTotalScrollY(wheelView.getTotalScrollY() + realOffset); + + //这里如果不是循环模式,则点击空白位置需要回滚,不然就会出现选到-1 item的 情况 + if (!wheelView.isLoop()) { + float itemHeight = wheelView.getItemHeight(); + float top = (float) (-wheelView.getInitPosition()) * itemHeight; + float bottom = (float) (wheelView.getItemsCount() - 1 - wheelView.getInitPosition()) * itemHeight; + if (wheelView.getTotalScrollY() <= top || wheelView.getTotalScrollY() >= bottom) { + wheelView.setTotalScrollY(wheelView.getTotalScrollY() - realOffset); + wheelView.cancelFuture(); + wheelView.getHandler().sendEmptyMessage(MessageHandler.WHAT_ITEM_SELECTED); + return; + } + } + wheelView.getHandler().sendEmptyMessage(MessageHandler.WHAT_INVALIDATE_LOOP_VIEW); + realTotalOffset = realTotalOffset - realOffset; + } + } +} diff --git a/wheelview/src/main/java/com/contrarywind/view/WheelView.java b/wheelview/src/main/java/com/contrarywind/view/WheelView.java new file mode 100644 index 0000000..fe306e4 --- /dev/null +++ b/wheelview/src/main/java/com/contrarywind/view/WheelView.java @@ -0,0 +1,822 @@ +package com.contrarywind.view; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.Typeface; +import android.os.Handler; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.util.DisplayMetrics; +import android.util.Log; +import android.view.GestureDetector; +import android.view.Gravity; +import android.view.MotionEvent; +import android.view.View; + +import com.contrarywind.adapter.WheelAdapter; +import com.contrarywind.interfaces.IPickerViewData; +import com.contrarywind.listener.LoopViewGestureListener; +import com.contrarywind.listener.OnItemSelectedListener; +import com.contrarywind.timer.InertiaTimerTask; +import com.contrarywind.timer.MessageHandler; +import com.contrarywind.timer.SmoothScrollTimerTask; + +import java.util.Locale; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +/** + * 3d滚轮控件 + */ +public class WheelView extends View { + + public enum ACTION { // 点击,滑翔(滑到尽头),拖拽事件 + CLICK, FLING, DAGGLE + } + + public enum DividerType { // 分隔线类型 + FILL, WRAP + } + + private DividerType dividerType;//分隔线类型 + + private Context context; + private Handler handler; + private GestureDetector gestureDetector; + private OnItemSelectedListener onItemSelectedListener; + + private boolean isOptions = false; + private boolean isCenterLabel = true; + + // Timer mTimer; + private ScheduledExecutorService mExecutor = Executors.newSingleThreadScheduledExecutor(); + private ScheduledFuture mFuture; + + private Paint paintOuterText; + private Paint paintCenterText; + private Paint paintIndicator; + + private WheelAdapter adapter; + + private String label;//附加单位 + private int textSize;//选项的文字大小 + private int maxTextWidth; + private int maxTextHeight; + private int textXOffset; + private float itemHeight;//每行高度 + + + private Typeface typeface = Typeface.MONOSPACE;//字体样式,默认是等宽字体 + private int textColorOut; + private int textColorCenter; + private int dividerColor; + + // 条目间距倍数 + private float lineSpacingMultiplier = 1.6F; + private boolean isLoop; + + // 第一条线Y坐标值 + private float firstLineY; + //第二条线Y坐标 + private float secondLineY; + //中间label绘制的Y坐标 + private float centerY; + + //当前滚动总高度y值 + private float totalScrollY; + + //初始化默认选中项 + private int initPosition; + + //选中的Item是第几个 + private int selectedItem; + private int preCurrentIndex; + //滚动偏移值,用于记录滚动了多少个item + private int change; + + // 绘制几个条目,实际上第一项和最后一项Y轴压缩成0%了,所以可见的数目实际为9 + private int itemsVisible = 11; + + private int measuredHeight;// WheelView 控件高度 + private int measuredWidth;// WheelView 控件宽度 + + // 半径 + private int radius; + + private int mOffset = 0; + private float previousY = 0; + private long startTime = 0; + + // 修改这个值可以改变滑行速度 + private static final int VELOCITY_FLING = 5; + private int widthMeasureSpec; + + private int mGravity = Gravity.CENTER; + private int drawCenterContentStart = 0;//中间选中文字开始绘制位置 + private int drawOutContentStart = 0;//非中间文字开始绘制位置 + private static final float SCALE_CONTENT = 0.8F;//非中间文字则用此控制高度,压扁形成3d错觉 + private float CENTER_CONTENT_OFFSET;//偏移量 + + private final float DEFAULT_TEXT_TARGET_SKEWX = 0.5f; + + public WheelView(Context context) { + this(context, null); + } + + public WheelView(Context context, AttributeSet attrs) { + super(context, attrs); + + textSize = getResources().getDimensionPixelSize(R.dimen.pickerview_textsize);//默认大小 + + DisplayMetrics dm = getResources().getDisplayMetrics(); + float density = dm.density; // 屏幕密度比(0.75/1.0/1.5/2.0/3.0) + + if (density < 1) {//根据密度不同进行适配 + CENTER_CONTENT_OFFSET = 2.4F; + } else if (1 <= density && density < 2) { + CENTER_CONTENT_OFFSET = 3.6F; + } else if (1 <= density && density < 2) { + CENTER_CONTENT_OFFSET = 4.5F; + } else if (2 <= density && density < 3) { + CENTER_CONTENT_OFFSET = 6.0F; + } else if (density >= 3) { + CENTER_CONTENT_OFFSET = density * 2.5F; + } + + if (attrs != null) { + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.pickerview, 0, 0); + mGravity = a.getInt(R.styleable.pickerview_wheelview_gravity, Gravity.CENTER); + textColorOut = a.getColor(R.styleable.pickerview_wheelview_textColorOut, 0xFFa8a8a8); + textColorCenter = a.getColor(R.styleable.pickerview_wheelview_textColorCenter, 0xFF2a2a2a); + dividerColor = a.getColor(R.styleable.pickerview_wheelview_dividerColor, 0xFFd5d5d5); + textSize = a.getDimensionPixelOffset(R.styleable.pickerview_wheelview_textSize, textSize); + lineSpacingMultiplier = a.getFloat(R.styleable.pickerview_wheelview_lineSpacingMultiplier, lineSpacingMultiplier); + a.recycle();//回收内存 + } + + judgeLineSpace(); + initLoopView(context); + } + + /** + * 判断间距是否在1.0-4.0之间 + */ + private void judgeLineSpace() { + if (lineSpacingMultiplier < 1.0f) { + lineSpacingMultiplier = 1.0f; + } else if (lineSpacingMultiplier > 4.0f) { + lineSpacingMultiplier = 4.0f; + } + } + + private void initLoopView(Context context) { + this.context = context; + handler = new MessageHandler(this); + gestureDetector = new GestureDetector(context, new LoopViewGestureListener(this)); + gestureDetector.setIsLongpressEnabled(false); + isLoop = true; + + totalScrollY = 0; + initPosition = -1; + initPaints(); + } + + private void initPaints() { + paintOuterText = new Paint(); + paintOuterText.setColor(textColorOut); + paintOuterText.setAntiAlias(true); + paintOuterText.setTypeface(typeface); + paintOuterText.setTextSize(textSize); + + paintCenterText = new Paint(); + paintCenterText.setColor(textColorCenter); + paintCenterText.setAntiAlias(true); + paintCenterText.setTextScaleX(1.1F); + paintCenterText.setTypeface(typeface); + paintCenterText.setTextSize(textSize); + + paintIndicator = new Paint(); + paintIndicator.setColor(dividerColor); + paintIndicator.setAntiAlias(true); + + setLayerType(LAYER_TYPE_SOFTWARE, null); + } + + private void remeasure() {//重新测量 + if (adapter == null) { + return; + } + + measureTextWidthHeight(); + + //半圆的周长 = item高度乘以item数目-1 + int halfCircumference = (int) (itemHeight * (itemsVisible - 1)); + //整个圆的周长除以PI得到直径,这个直径用作控件的总高度 + measuredHeight = (int) ((halfCircumference * 2) / Math.PI); + //求出半径 + radius = (int) (halfCircumference / Math.PI); + //控件宽度,这里支持weight + measuredWidth = MeasureSpec.getSize(widthMeasureSpec); + //计算两条横线 和 选中项画笔的基线Y位置 + firstLineY = (measuredHeight - itemHeight) / 2.0F; + secondLineY = (measuredHeight + itemHeight) / 2.0F; + centerY = secondLineY - (itemHeight - maxTextHeight) / 2.0f - CENTER_CONTENT_OFFSET; + + //初始化显示的item的position + if (initPosition == -1) { + if (isLoop) { + initPosition = (adapter.getItemsCount() + 1) / 2; + } else { + initPosition = 0; + } + } + preCurrentIndex = initPosition; + } + + /** + * 计算最大length的Text的宽高度 + */ + private void measureTextWidthHeight() { + Rect rect = new Rect(); + for (int i = 0; i < adapter.getItemsCount(); i++) { + String s1 = getContentText(adapter.getItem(i)); + paintCenterText.getTextBounds(s1, 0, s1.length(), rect); + + int textWidth = rect.width(); + + if (textWidth > maxTextWidth) { + maxTextWidth = textWidth; + } + paintCenterText.getTextBounds("\u661F\u671F", 0, 2, rect); // 星期的字符编码(以它为标准高度) + + maxTextHeight = rect.height() + 2; + + } + itemHeight = lineSpacingMultiplier * maxTextHeight; + } + + public void smoothScroll(ACTION action) {//平滑滚动的实现 + cancelFuture(); + if (action == ACTION.FLING || action == ACTION.DAGGLE) { + mOffset = (int) ((totalScrollY % itemHeight + itemHeight) % itemHeight); + if ((float) mOffset > itemHeight / 2.0F) {//如果超过Item高度的一半,滚动到下一个Item去 + mOffset = (int) (itemHeight - (float) mOffset); + } else { + mOffset = -mOffset; + } + } + //停止的时候,位置有偏移,不是全部都能正确停止到中间位置的,这里把文字位置挪回中间去 + mFuture = mExecutor.scheduleWithFixedDelay(new SmoothScrollTimerTask(this, mOffset), 0, 10, TimeUnit.MILLISECONDS); + } + + public final void scrollBy(float velocityY) {//滚动惯性的实现 + cancelFuture(); + mFuture = mExecutor.scheduleWithFixedDelay(new InertiaTimerTask(this, velocityY), 0, VELOCITY_FLING, TimeUnit.MILLISECONDS); + } + + public void cancelFuture() { + if (mFuture != null && !mFuture.isCancelled()) { + mFuture.cancel(true); + mFuture = null; + } + } + + /** + * 设置是否循环滚动 + * + * @param cyclic 是否循环 + */ + public final void setCyclic(boolean cyclic) { + isLoop = cyclic; + } + + public final void setTypeface(Typeface font) { + typeface = font; + paintOuterText.setTypeface(typeface); + paintCenterText.setTypeface(typeface); + } + + public final void setTextSize(float size) { + if (size > 0.0F) { + textSize = (int) (context.getResources().getDisplayMetrics().density * size); + paintOuterText.setTextSize(textSize); + paintCenterText.setTextSize(textSize); + } + } + + public final void setCurrentItem(int currentItem) { + //不添加这句,当这个wheelView不可见时,默认都是0,会导致获取到的时间错误 + this.selectedItem = currentItem; + this.initPosition = currentItem; + totalScrollY = 0;//回归顶部,不然重设setCurrentItem的话位置会偏移的,就会显示出不对位置的数据 + invalidate(); + } + + public final void setOnItemSelectedListener(OnItemSelectedListener OnItemSelectedListener) { + this.onItemSelectedListener = OnItemSelectedListener; + } + + public final void setAdapter(WheelAdapter adapter) { + this.adapter = adapter; + remeasure(); + invalidate(); + } + + public final WheelAdapter getAdapter() { + return adapter; + } + + public final int getCurrentItem() { + return selectedItem; + } + + public final void onItemSelected() { + if (onItemSelectedListener != null) { + postDelayed(new Runnable() { + @Override + public void run() { + onItemSelectedListener.onItemSelected(getCurrentItem()); + } + }, 200L); + } + } + + @Override + protected void onDraw(Canvas canvas) { + if (adapter == null) { + return; + } + //initPosition越界会造成preCurrentIndex的值不正确 + initPosition = Math.min(Math.max(0, initPosition), adapter.getItemsCount() - 1); + + //可见的item数组 + @SuppressLint("DrawAllocation") + Object visibles[] = new Object[itemsVisible]; + //滚动的Y值高度除去每行Item的高度,得到滚动了多少个item,即change数 + change = (int) (totalScrollY / itemHeight); + // Log.d("change", "" + change); + + try { + //滚动中实际的预选中的item(即经过了中间位置的item) = 滑动前的位置 + 滑动相对位置 + preCurrentIndex = initPosition + change % adapter.getItemsCount(); + + } catch (ArithmeticException e) { + Log.e("WheelView", "出错了!adapter.getItemsCount() == 0,联动数据不匹配"); + } + if (!isLoop) {//不循环的情况 + if (preCurrentIndex < 0) { + preCurrentIndex = 0; + } + if (preCurrentIndex > adapter.getItemsCount() - 1) { + preCurrentIndex = adapter.getItemsCount() - 1; + } + } else {//循环 + if (preCurrentIndex < 0) {//举个例子:如果总数是5,preCurrentIndex = -1,那么preCurrentIndex按循环来说,其实是0的上面,也就是4的位置 + preCurrentIndex = adapter.getItemsCount() + preCurrentIndex; + } + if (preCurrentIndex > adapter.getItemsCount() - 1) {//同理上面,自己脑补一下 + preCurrentIndex = preCurrentIndex - adapter.getItemsCount(); + } + } + //跟滚动流畅度有关,总滑动距离与每个item高度取余,即并不是一格格的滚动,每个item不一定滚到对应Rect里的,这个item对应格子的偏移值 + float itemHeightOffset = (totalScrollY % itemHeight); + + // 设置数组中每个元素的值 + int counter = 0; + while (counter < itemsVisible) { + int index = preCurrentIndex - (itemsVisible / 2 - counter);//索引值,即当前在控件中间的item看作数据源的中间,计算出相对源数据源的index值 + //判断是否循环,如果是循环数据源也使用相对循环的position获取对应的item值,如果不是循环则超出数据源范围使用""空白字符串填充,在界面上形成空白无数据的item项 + if (isLoop) { + index = getLoopMappingIndex(index); + visibles[counter] = adapter.getItem(index); + } else if (index < 0) { + visibles[counter] = ""; + } else if (index > adapter.getItemsCount() - 1) { + visibles[counter] = ""; + } else { + visibles[counter] = adapter.getItem(index); + } + + counter++; + + } + + //绘制中间两条横线 + if (dividerType == DividerType.WRAP) {//横线长度仅包裹内容 + float startX; + float endX; + + if (TextUtils.isEmpty(label)) {//隐藏Label的情况 + startX = (measuredWidth - maxTextWidth) / 2 - 12; + } else { + startX = (measuredWidth - maxTextWidth) / 4 - 12; + } + + if (startX <= 0) {//如果超过了WheelView的边缘 + startX = 10; + } + endX = measuredWidth - startX; + canvas.drawLine(startX, firstLineY, endX, firstLineY, paintIndicator); + canvas.drawLine(startX, secondLineY, endX, secondLineY, paintIndicator); + } else { + canvas.drawLine(0.0F, firstLineY, measuredWidth, firstLineY, paintIndicator); + canvas.drawLine(0.0F, secondLineY, measuredWidth, secondLineY, paintIndicator); + } + + //只显示选中项Label文字的模式,并且Label文字不为空,则进行绘制 + if (!TextUtils.isEmpty(label) && isCenterLabel) { + //绘制文字,靠右并留出空隙 + int drawRightContentStart = measuredWidth - getTextWidth(paintCenterText, label); + canvas.drawText(label, drawRightContentStart - CENTER_CONTENT_OFFSET, centerY, paintCenterText); + } + + counter = 0; + while (counter < itemsVisible) { + canvas.save(); + // 弧长 L = itemHeight * counter - itemHeightOffset + // 求弧度 α = L / r (弧长/半径) [0,π] + double radian = ((itemHeight * counter - itemHeightOffset)) / radius; + // 弧度转换成角度(把半圆以Y轴为轴心向右转90度,使其处于第一象限及第四象限 + // angle [-90°,90°] + float angle = (float) (90D - (radian / Math.PI) * 180D);//item第一项,从90度开始,逐渐递减到 -90度 + + // 计算取值可能有细微偏差,保证负90°到90°以外的不绘制 + if (angle >= 90F || angle <= -90F) { + canvas.restore(); + } else { + // 根据当前角度计算出偏差系数,用以在绘制时控制文字的 水平移动 透明度 倾斜程度 + float offsetCoefficient = (float) Math.pow(Math.abs(angle) / 90f, 2.2); + //获取内容文字 + String contentText; + + //如果是label每项都显示的模式,并且item内容不为空、label 也不为空 + if (!isCenterLabel && !TextUtils.isEmpty(label) && !TextUtils.isEmpty(getContentText(visibles[counter]))) { + contentText = getContentText(visibles[counter]) + label; + } else { + contentText = getContentText(visibles[counter]); + } + + reMeasureTextSize(contentText); + //计算开始绘制的位置 + measuredCenterContentStart(contentText); + measuredOutContentStart(contentText); + float translateY = (float) (radius - Math.cos(radian) * radius - (Math.sin(radian) * maxTextHeight) / 2D); + //根据Math.sin(radian)来更改canvas坐标系原点,然后缩放画布,使得文字高度进行缩放,形成弧形3d视觉差 + canvas.translate(0.0F, translateY); +// canvas.scale(1.0F, (float) Math.sin(radian)); + if (translateY <= firstLineY && maxTextHeight + translateY >= firstLineY) { + // 条目经过第一条线 + canvas.save(); + canvas.clipRect(0, 0, measuredWidth, firstLineY - translateY); + canvas.scale(1.0F, (float) Math.sin(radian) * SCALE_CONTENT); + canvas.drawText(contentText, drawOutContentStart, maxTextHeight, paintOuterText); + canvas.restore(); + canvas.save(); + canvas.clipRect(0, firstLineY - translateY, measuredWidth, (int) (itemHeight)); + canvas.scale(1.0F, (float) Math.sin(radian) * 1.0F); + canvas.drawText(contentText, drawCenterContentStart, maxTextHeight - CENTER_CONTENT_OFFSET, paintCenterText); + canvas.restore(); + } else if (translateY <= secondLineY && maxTextHeight + translateY >= secondLineY) { + // 条目经过第二条线 + canvas.save(); + canvas.clipRect(0, 0, measuredWidth, secondLineY - translateY); + canvas.scale(1.0F, (float) Math.sin(radian) * 1.0F); + canvas.drawText(contentText, drawCenterContentStart, maxTextHeight - CENTER_CONTENT_OFFSET, paintCenterText); + canvas.restore(); + canvas.save(); + canvas.clipRect(0, secondLineY - translateY, measuredWidth, (int) (itemHeight)); + canvas.scale(1.0F, (float) Math.sin(radian) * SCALE_CONTENT); + canvas.drawText(contentText, drawOutContentStart, maxTextHeight, paintOuterText); + canvas.restore(); + } else if (translateY >= firstLineY && maxTextHeight + translateY <= secondLineY) { + // 中间条目 + canvas.clipRect(0, 0, measuredWidth, maxTextHeight); + //让文字居中 + float Y = maxTextHeight - CENTER_CONTENT_OFFSET;//因为圆弧角换算的向下取值,导致角度稍微有点偏差,加上画笔的基线会偏上,因此需要偏移量修正一下 + canvas.drawText(contentText, drawCenterContentStart, Y, paintCenterText); + + //设置选中项 + selectedItem = preCurrentIndex - (itemsVisible / 2 - counter); + + } else { + // 其他条目 + canvas.save(); + canvas.clipRect(0, 0, measuredWidth, (int) (itemHeight)); + canvas.scale(1.0F, (float) Math.sin(radian) * SCALE_CONTENT); + // 控制文字倾斜角度 + paintOuterText.setTextSkewX((textXOffset == 0 ? 0 : (textXOffset > 0 ? 1 : -1)) * (angle > 0 ? -1 : 1) * DEFAULT_TEXT_TARGET_SKEWX * offsetCoefficient); + // 控制透明度 + paintOuterText.setAlpha((int) ((1 - offsetCoefficient) * 255)); + // 控制文字水平偏移距离 + canvas.drawText(contentText, drawOutContentStart + textXOffset * offsetCoefficient, maxTextHeight, paintOuterText); + canvas.restore(); + } + canvas.restore(); + paintCenterText.setTextSize(textSize); + } + counter++; + } + } + + /** + * reset the size of the text Let it can fully display + * + * @param contentText item text content. + */ + private void reMeasureTextSize(String contentText) { + Rect rect = new Rect(); + paintCenterText.getTextBounds(contentText, 0, contentText.length(), rect); + int width = rect.width(); + int size = textSize; + while (width > measuredWidth) { + size--; + //设置2条横线中间的文字大小 + paintCenterText.setTextSize(size); + paintCenterText.getTextBounds(contentText, 0, contentText.length(), rect); + width = rect.width(); + } + //设置2条横线外面的文字大小 + paintOuterText.setTextSize(size); + } + + + //递归计算出对应的index + private int getLoopMappingIndex(int index) { + if (index < 0) { + index = index + adapter.getItemsCount(); + index = getLoopMappingIndex(index); + } else if (index > adapter.getItemsCount() - 1) { + index = index - adapter.getItemsCount(); + index = getLoopMappingIndex(index); + } + return index; + } + + /** + * 获取所显示的数据源 + * + * @param item data resource + * @return 对应显示的字符串 + */ + private String getContentText(Object item) { + if (item == null) { + return ""; + } else if (item instanceof IPickerViewData) { + return ((IPickerViewData) item).getPickerViewText(); + } else if (item instanceof Integer) { + //如果为整形则最少保留两位数. + return String.format(Locale.getDefault(), "%02d", (int) item); + } + return item.toString(); + } + + private void measuredCenterContentStart(String content) { + Rect rect = new Rect(); + paintCenterText.getTextBounds(content, 0, content.length(), rect); + switch (mGravity) { + case Gravity.CENTER://显示内容居中 + if (isOptions || label == null || label.equals("") || !isCenterLabel) { + drawCenterContentStart = (int) ((measuredWidth - rect.width()) * 0.5); + } else {//只显示中间label时,时间选择器内容偏左一点,留出空间绘制单位标签 + drawCenterContentStart = (int) ((measuredWidth - rect.width()) * 0.25); + } + break; + case Gravity.LEFT: + drawCenterContentStart = 0; + break; + case Gravity.RIGHT://添加偏移量 + drawCenterContentStart = measuredWidth - rect.width() - (int) CENTER_CONTENT_OFFSET; + break; + } + } + + private void measuredOutContentStart(String content) { + Rect rect = new Rect(); + paintOuterText.getTextBounds(content, 0, content.length(), rect); + switch (mGravity) { + case Gravity.CENTER: + if (isOptions || label == null || label.equals("") || !isCenterLabel) { + drawOutContentStart = (int) ((measuredWidth - rect.width()) * 0.5); + } else {//只显示中间label时,时间选择器内容偏左一点,留出空间绘制单位标签 + drawOutContentStart = (int) ((measuredWidth - rect.width()) * 0.25); + } + break; + case Gravity.LEFT: + drawOutContentStart = 0; + break; + case Gravity.RIGHT: + drawOutContentStart = measuredWidth - rect.width() - (int) CENTER_CONTENT_OFFSET; + break; + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + this.widthMeasureSpec = widthMeasureSpec; + remeasure(); + setMeasuredDimension(measuredWidth, measuredHeight); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + boolean eventConsumed = gestureDetector.onTouchEvent(event); + boolean isIgnore = false;//超过边界滑动时,不再绘制UI。 + + float top = -initPosition * itemHeight; + float bottom = (adapter.getItemsCount() - 1 - initPosition) * itemHeight; + float ratio = 0.25f; + + switch (event.getAction()) { + //按下 + case MotionEvent.ACTION_DOWN: + startTime = System.currentTimeMillis(); + cancelFuture(); + previousY = event.getRawY(); + break; + //滑动中 + case MotionEvent.ACTION_MOVE: + + float dy = previousY - event.getRawY(); + previousY = event.getRawY(); + totalScrollY = totalScrollY + dy; + + // 非循环模式下,边界处理。 + if (!isLoop) { + if ((totalScrollY - itemHeight * ratio < top && dy < 0) + || (totalScrollY + itemHeight * ratio > bottom && dy > 0)) { + //快滑动到边界了,设置已滑动到边界的标志 + totalScrollY -= dy; + isIgnore = true; + }/* else if (totalScrollY + itemHeight * ratio > bottom && dy > 0) { + totalScrollY -= dy; + isIgnore = true; + } */else { + isIgnore = false; + } + } + break; + + case MotionEvent.ACTION_UP: + default: + + if (!eventConsumed) {//未消费掉事件 + + /** + *@describe <关于弧长的计算> + * + * 弧长公式: L = α*R + * 反余弦公式:arccos(cosα) = α + * 由于之前是有顺时针偏移90度, + * 所以实际弧度范围α2的值 :α2 = π/2-α (α=[0,π] α2 = [-π/2,π/2]) + * 根据正弦余弦转换公式 cosα = sin(π/2-α) + * 代入,得: cosα = sin(π/2-α) = sinα2 = (R - y) / R + * 所以弧长 L = arccos(cosα)*R = arccos((R - y) / R)*R + */ + + float y = event.getY(); + double L = Math.acos((radius - y) / radius) * radius; + //item0 有一半是在不可见区域,所以需要加上 itemHeight / 2 + int circlePosition = (int) ((L + itemHeight / 2) / itemHeight); + float extraOffset = (totalScrollY % itemHeight + itemHeight) % itemHeight; + //已滑动的弧长值 + mOffset = (int) ((circlePosition - itemsVisible / 2) * itemHeight - extraOffset); + + if ((System.currentTimeMillis() - startTime) > 120) { + // 处理拖拽事件 + smoothScroll(ACTION.DAGGLE); + } else { + // 处理条目点击事件 + smoothScroll(ACTION.CLICK); + } + } + break; + } + if (!isIgnore && event.getAction() != MotionEvent.ACTION_DOWN) { + invalidate(); + } + return true; + } + + /** + * 获取Item个数 + * + * @return item个数 + */ + public int getItemsCount() { + return adapter != null ? adapter.getItemsCount() : 0; + } + + /** + * 附加在右边的单位字符串 + * + * @param label 单位 + */ + public void setLabel(String label) { + this.label = label; + } + + public void isCenterLabel(boolean isCenterLabel) { + this.isCenterLabel = isCenterLabel; + } + + public void setGravity(int gravity) { + this.mGravity = gravity; + } + + public int getTextWidth(Paint paint, String str) {//计算文字宽度 + int iRet = 0; + if (str != null && str.length() > 0) { + int len = str.length(); + float[] widths = new float[len]; + paint.getTextWidths(str, widths); + for (int j = 0; j < len; j++) { + iRet += (int) Math.ceil(widths[j]); + } + } + return iRet; + } + + public void setIsOptions(boolean options) { + isOptions = options; + } + + + public void setTextColorOut(int textColorOut) { + if (textColorOut != 0) { + this.textColorOut = textColorOut; + paintOuterText.setColor(this.textColorOut); + } + } + + public void setTextColorCenter(int textColorCenter) { + if (textColorCenter != 0) { + + this.textColorCenter = textColorCenter; + paintCenterText.setColor(this.textColorCenter); + } + } + + public void setTextXOffset(int textXOffset) { + this.textXOffset = textXOffset; + if (textXOffset != 0) { + paintCenterText.setTextScaleX(1.0f); + } + } + + public void setDividerColor(int dividerColor) { + if (dividerColor != 0) { + this.dividerColor = dividerColor; + paintIndicator.setColor(this.dividerColor); + } + } + + public void setDividerType(DividerType dividerType) { + this.dividerType = dividerType; + } + + public void setLineSpacingMultiplier(float lineSpacingMultiplier) { + if (lineSpacingMultiplier != 0) { + this.lineSpacingMultiplier = lineSpacingMultiplier; + judgeLineSpace(); + } + } + + public boolean isLoop() { + return isLoop; + } + + public float getTotalScrollY() { + return totalScrollY; + } + + public void setTotalScrollY(float totalScrollY) { + this.totalScrollY = totalScrollY; + } + + public float getItemHeight() { + return itemHeight; + } + + public void setItemHeight(float itemHeight) { + this.itemHeight = itemHeight; + } + + public int getInitPosition() { + return initPosition; + } + + public void setInitPosition(int initPosition) { + this.initPosition = initPosition; + } + + @Override + public Handler getHandler() { + return handler; + } +} \ No newline at end of file diff --git a/wheelview/src/main/res/values/attrs.xml b/wheelview/src/main/res/values/attrs.xml new file mode 100644 index 0000000..142e2a3 --- /dev/null +++ b/wheelview/src/main/res/values/attrs.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/wheelview/src/main/res/values/colors.xml b/wheelview/src/main/res/values/colors.xml new file mode 100644 index 0000000..cdc3058 --- /dev/null +++ b/wheelview/src/main/res/values/colors.xml @@ -0,0 +1,14 @@ + + + #057dff + #c2daf5 + #f5f5f5 + + #000000 + #a8a8a8 + #2a2a2a + #d5d5d5 + #60000000 + #FFFFFFFF + + diff --git a/wheelview/src/main/res/values/dimens.xml b/wheelview/src/main/res/values/dimens.xml new file mode 100644 index 0000000..63f8dd8 --- /dev/null +++ b/wheelview/src/main/res/values/dimens.xml @@ -0,0 +1,13 @@ + + + 44dp + + + 20dp + + + 17sp + 18sp + + 20sp + diff --git a/wheelview/src/main/res/values/strings.xml b/wheelview/src/main/res/values/strings.xml new file mode 100644 index 0000000..dc526b1 --- /dev/null +++ b/wheelview/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + wheelview + -- libgit2 0.21.0