Commit af44c142 by zhuhuaqiang

inti

parents
/build
*.iml
/src/main/gen
# RefrashLayout
---
##example
**xml**
<com.x.leo.refrashviews.RefreshLayout
android:layout_width="match_parent"
android:background="@color/colorPrimary"
android:id = "@+id/rf_myloan"
xmlns:app="http://schemas.android.com/apk/res-auto"
app:mainView="@+id/rv_loan"
app:topView="@+id/top_view"
android:layout_height="match_parent">
<TextView
android:layout_width="match_parent"
android:id = "@+id/top_view"
style="@style/text_14dp_white"
android:text="@string/textview_refrash"
android:gravity="center"
android:layout_height="@dimen/dp120" />
<android.support.v7.widget.RecyclerView
android:id="@+id/rv"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center_horizontal"
android:paddingTop="@dimen/dp20"
></android.support.v7.widget.RecyclerView>
</com.x.leo.refrashviews.RefreshLayout>
  **java**
refrashLayout.setOnRefrashListener(new OnRefrashAdapter() {
@Override
public void onTopRefrash() {
mPresenter.initLoanData();
}
@Override
public void onBottomRefrash() {
}
});
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
defaultConfig {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile "com.android.support:appcompat-v7:$rootProject.ext.supportLibraryVersion"
testCompile 'junit:junit:4.12'
compile "org.jetbrains.kotlin:kotlin-stdlib:$rootProject.ext.kotlin_version"
compile "com.android.support:design:$rootProject.ext.supportLibraryVersion"
}
repositories {
mavenCentral()
}
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in D:\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
package com.x.leo.refrashviews;
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 <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() throws Exception {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getTargetContext();
assertEquals("com.x.leo.refrashviews.test", appContext.getPackageName());
}
}
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.x.leo.refrashviews">
<application android:allowBackup="true" android:label="@string/app_name"
android:supportsRtl="true">
</application>
</manifest>
package com.x.leo.refrashviews
import android.content.Context
import android.graphics.Rect
import android.support.v4.widget.ViewDragHelper
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.support.v7.widget.StaggeredGridLayoutManager
import android.util.AttributeSet
import android.util.Log
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.widget.AdapterView
import android.widget.ListView
import java.io.Serializable
/**
* @作者:XLEO
* @创建日期: 2017/8/25 10:51
* @描述:${TODO}
*
* @更新者:${Author}$
* @更新时间:${Date}$
* @更新描述:${TODO}
* @下一步:
*/
class RefreshLayout(ctx: Context, attr: AttributeSet?) : ViewGroup(ctx, attr) {
constructor(ctx: Context) : this(ctx, null)
private var topRefrashViewId: Int = -1
private var bottomRefrashViewId: Int = -1
private var mainViewId: Int = -1
private var topRefrashView: View? = null
private var bottomView: View? = null
private var mainView: View? = null
private var isTopRefrash: Boolean = false
private var isBottomRefrash: Boolean = false
var onRefrashListener: OnRefrashListener? = null
companion object {
const val STATEDIRECTION = 0x0012.shl(3)
const val STATEIDLE = 0x0011.shl(3)
const val STATEDRAGING = 0x0013.shl(3)
const val STATENOTDRAG = 0x0014.shl(3)
const val DIR_NONE: Int = 0x1122
const val DIR_DOWN: Int = 0x1123
const val DIR_UP: Int = 0x1124
const val DO_LOG = false
}
private var currentState = STATEIDLE
private var direction: Int = DIR_NONE
private var offset: Int = 0
private var doReDispatchEvent: Boolean = false
private val dragHelper: ViewDragHelper by lazy {
ViewDragHelper.create(this, 1.0f, object : ViewDragHelper.Callback() {
override fun tryCaptureView(child: View, pointerId: Int): Boolean {
if (child!! == mainView) {
return true
}
return false
}
override fun clampViewPositionVertical(child: View, top: Int, dy: Int): Int {
var realTop = top
if (doSetOffset) {
val newTop = top - offset
doSetOffset = false
realTop = newTop
logd("==top:" + realTop + ";dy:" + dy + ";threadId:" + Thread.currentThread().id + ";isTopRefrash:" + isTopRefrash + ";currentState:" + currentState + ";time:" + System.currentTimeMillis())
}
logd("top:" + realTop + ";dy:" + dy + ";isTopRefrash:" + isTopRefrash + ";currentState:" + currentState + ";time:" + System.currentTimeMillis())
if (isTopRefrash && realTop < 0) {
realTop = 0
doReDispatchEvent = true
} else if (isBottomRefrash && realTop > 0) {
realTop = 0
doReDispatchEvent = true
} else {
if (!(isTopRefrashable() && realTop >= 0) && !(isBottomRefrashable() && realTop <= 0)) {
realTop = 0
doReDispatchEvent = true
}
}
return realTop
}
override fun clampViewPositionHorizontal(child: View, left: Int, dx: Int): Int {
return child!!.left
}
override fun onViewReleased(releasedChild: View, xvel: Float, yvel: Float) {
resetViews()
}
override fun onViewPositionChanged(changedView: View, left: Int, top: Int, dx: Int, dy: Int) {
// logd("newtop:" + top + ";dy:" + dy)
locateViews(top)
if (doReDispatchEvent) {
//redispatchEvent()
doReDispatchEvent = false
}
}
})
}
private fun redispatchEvent() {
getDirection()
isDirectionPermited()
when (currentState) {
STATEDRAGING -> {
if (dragHelper.activePointerId == localEvent!!.getPointerId(localEvent!!.actionIndex))
dragHelper.processTouchEvent(localEvent!!)
}
STATENOTDRAG -> {
mainView!!.dispatchTouchEvent(localEvent)
}
else -> {
}
}
}
private fun locateViews(dy: Int) {
when (dy > 0) {
true -> {
if (isBottomRefrash) {
bottomMoveBy(dy)
} else {
if (topRefrashView != null) {
topMoveBy(dy)
isTopRefrash = true
}
}
}
else -> {
if (isTopRefrash) {
topMoveBy(dy)
} else {
if (bottomView != null) {
bottomMoveBy(dy)
isBottomRefrash = true
}
}
}
}
}
private var isReleaseing: Boolean = false
override fun computeScroll() {
if (dragHelper.continueSettling(true)) {
invalidate()
} else {
if (isReleaseing) {
isBottomRefrash = false
isTopRefrash = false
isReleaseing = false
}
}
}
private fun resetViews() {
logd("reset")
isReleaseing = true
if (mainView != null) {
dragHelper.settleCapturedViewAt(paddingLeft, paddingTop)
}
invalidate()
if (isTopRefrash) {
if (topRefrashView!!.translationY > topViewHeight * 1 / 2)
onRefrashListener?.onTopRefrash()
}
if (isBottomRefrash) {
if ((-bottomView!!.translationY) > bottomViewHeight * 1 / 2)
onRefrashListener?.onBottomRefrash()
}
}
private fun topMoveBy(dy: Int) {
if (dy <= 0) {
topRefrashView!!.translationY = 0f
//logd("topview:transY:" + topRefrashView!!.translationY)
onRefrashListener?.onTopViewMove(0)
isTopRefrash = false
locateViews(dy)
} else {
topRefrashView!!.translationY = dy.toFloat()
//logd("topview:transY:" + topRefrashView!!.translationY)
onRefrashListener?.onTopViewMove(dy)
}
}
private fun bottomMoveBy(dy: Int) {
if (dy >= 0) {
bottomView!!.translationY = 0f
onRefrashListener?.onBottomViewMove(0)
isBottomRefrash = false
locateViews(dy)
} else {
bottomView!!.translationY = dy.toFloat()
onRefrashListener?.onBottomViewMove(dy)
}
}
init {
if (attr != null) {
val attrs = ctx.obtainStyledAttributes(attr!!, R.styleable.RefreshLayout)
topRefrashViewId = attrs.getResourceId(R.styleable.RefreshLayout_topView, -1)
bottomRefrashViewId = attrs.getResourceId(R.styleable.RefreshLayout_bottomView, -1)
mainViewId = attrs.getResourceId(R.styleable.RefreshLayout_mainView, -1)
}
}
fun logd(s: String) {
if (DO_LOG) {
Log.d("RefreshLayout", s)
}
}
override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
if (ev != null) {
dragHelper.shouldInterceptTouchEvent(ev)
}
return true
}
private fun isDirectionPermited(): Boolean {
when (mainView) {
is AdapterView<*> -> {
val listView = mainView as ListView
if (listView.adapter == null || listView.adapter.count <= 0) {
currentState = STATEDRAGING
return true
}
if (listView.firstVisiblePosition == 0 && isChildTopVisible(listView.getChildAt(0)) && isTopRefrashable()) {
currentState = STATEDRAGING
return true
}
if (listView.lastVisiblePosition == listView.adapter.count - 1 && isChildBottomVisible(listView.getChildAt(listView.childCount - 1)) && isBottomRefrashable()) {
currentState = STATEDRAGING
return true
}
}
is RecyclerView -> {
val recycler = mainView as RecyclerView
if (recycler.layoutManager is LinearLayoutManager) {
if (recycler.adapter == null && direction != DIR_NONE) {
currentState = STATEDRAGING
return true
}
val linearManager = recycler.layoutManager as LinearLayoutManager
//获取第一个可见view的位置
val firstItemPosition = linearManager.findFirstCompletelyVisibleItemPosition()
if (firstItemPosition <= 0 && isTopRefrashable()) {
changeToDragState()
return true
}
//获取最后一个可见view的位置
val lastItemPosition = linearManager.findLastCompletelyVisibleItemPosition()
if ((recycler.adapter == null || recycler.adapter.itemCount == 0 || lastItemPosition == recycler.adapter.itemCount - 1) && isBottomRefrashable()) {
changeToDragState()
return true
}
} else if (recycler.layoutManager is StaggeredGridLayoutManager) {
val stagger = recycler.layoutManager as StaggeredGridLayoutManager
if (stagger.findViewByPosition(0).windowVisibility != View.GONE && isTopRefrashable()) {
changeToDragState()
return true
} else if ((recycler.adapter == null || recycler.adapter.itemCount == 0 || stagger.findViewByPosition(recycler.adapter.itemCount - 1).windowVisibility != View.GONE) && isBottomRefrashable()) {
changeToDragState()
return true
}
}
}
is ViewGroup -> {
val view = mainView as ViewGroup
if (view.childCount <= 0) {
if (direction != DIR_NONE) {
changeToDragState()
return true
} else {
changeToNotDragState()
return false
}
}
if (view.getChildAt(0).windowVisibility != View.GONE && isTopRefrashable()) {
changeToDragState()
return true
} else if (view.getChildAt(view.childCount - 1).windowVisibility != View.GONE && isBottomRefrashable()) {
changeToDragState()
return true
}
}
else -> {
changeToDragState()
return true
}
}
changeToNotDragState()
return false
}
private fun isChildTopVisible(childAt: View?): Boolean {
if (childAt == null) {
return false
}
return try {
val mainRect = Rect()
mainView!!.getGlobalVisibleRect(mainRect)
val rect = Rect()
childAt.getGlobalVisibleRect(rect)
Log.d("Visible", "main:" + mainRect.toString() + ";current:" + rect.toString())
(rect.top >= mainRect.top && rect.height() >= childAt.height - 1) || mainRect.height() < mainView!!.height
} catch (e: Throwable) {
true
}
}
private fun isChildBottomVisible(childAt: View?): Boolean {
if (childAt == null) {
return false
}
return try {
val mainRect = Rect()
mainView!!.getGlobalVisibleRect(mainRect)
val rect = Rect()
childAt.getGlobalVisibleRect(rect)
(rect.bottom <= mainRect.bottom && rect.height() >= childAt.height - 1) || mainRect.height() < mainView!!.height
} catch (e: Throwable) {
true
}
}
private fun changeToNotDragState() {
currentState = STATENOTDRAG
}
private fun isBottomRefrashable(): Boolean {
return if (bottomView == null) {
false
} else bottomView!!.translationY < 0 || direction == DIR_UP
}
private fun isTopRefrashable(): Boolean {
return if (topRefrashView == null) {
false
} else direction == DIR_DOWN || (topRefrashView != null && topRefrashView!!.translationY > 0)
}
private val eventLog: EventLogHolder by lazy {
EventLogHolder()
}
class EventLogHolder {
var start: MotionEventBean? = null
var last: MotionEventBean? = null
var current: MotionEventBean? = null
fun initStart(event: MotionEvent) {
start = MotionEventBean(event.actionMasked, event.x, event.y)
addEvent(event)
}
fun addEvent(event: MotionEvent) {
if (current != null) {
moveCurrentToLast()
current!!.updateDatas(event)
} else {
current = MotionEventBean(event.actionMasked, event.x, event.y)
}
}
private fun moveCurrentToLast() {
if (last == null) {
last = MotionEventBean(current!!.type, current!!.x, current!!.y)
} else {
last!!.updateDatas(current!!)
}
}
fun clearHolder() {
start = null
last = null
current = null
}
}
private var localEvent: MotionEvent? = null
override fun onTouchEvent(event: MotionEvent?): Boolean {
when (event?.actionMasked) {
MotionEvent.ACTION_DOWN -> {
eventLog.initStart(event)
if (isReleaseing) {
changeToDragState()
dragHelper.processTouchEvent(event)
} else {
isReleaseing = false
mainView!!.dispatchTouchEvent(event)
dragHelper.processTouchEvent(event)
currentState = STATEDIRECTION
logd("down" + ";threadId:" + Thread.currentThread().id)
}
}
MotionEvent.ACTION_MOVE -> {
if (event.actionIndex == 0) {
eventLog.addEvent(event)
localEvent = event
redispatchEvent()
isReleaseing = false
// logd("move")
}
}
MotionEvent.ACTION_UP -> {
if (currentState == STATEDRAGING) {
dragHelper.processTouchEvent(event)
} else if (currentState == STATENOTDRAG) {
mainView!!.dispatchTouchEvent(event)
} else {
mainView!!.dispatchTouchEvent(event)
}
currentState = STATEIDLE
direction = DIR_NONE
doSetOffset = false
eventLog.clearHolder()
logd("up" + ";threadId:" + Thread.currentThread().id)
}
else -> {
logd("else_state" + event!!.action)
}
}
return true
}
private fun changeToDragState() {
if (currentState == STATENOTDRAG) {
calcuteOffset()
}
currentState = STATEDRAGING
}
private var doSetOffset: Boolean = false
fun calcuteOffset() {
offset = ((eventLog.last!!.y - eventLog.start!!.y) + 0.5f).toInt()
if (direction == DIR_DOWN && offset > 0) {
doSetOffset = true
logd("offset:" + offset + ";threadId:" + Thread.currentThread().id + ":startY:" + eventLog.start!!.y + ";currY:" + eventLog.last!!.y + ";dir:" + direction + ";time:" + System.currentTimeMillis())
} else if (direction == DIR_UP && offset < 0) {
doSetOffset = true
logd("offset:" + offset + ";threadId:" + Thread.currentThread().id + ":startY:" + eventLog.start!!.y + ";currY:" + eventLog.last!!.y + ";dir:" + direction + ";time:" + System.currentTimeMillis())
} else {
doSetOffset = false
}
}
private var isDirectionChanged: Boolean = false
private fun getDirection() {
if (eventLog.last != null) {
var newDirection = DIR_NONE
val abs = Math.abs(eventLog.current!!.y - eventLog.last!!.y)
if (abs == 0.0f) {
newDirection = DIR_NONE
} else {
val fl = Math.abs(eventLog.current!!.x - eventLog.last!!.x) / abs
if (fl <= 0.5f) {
if (eventLog.last!!.y > eventLog.current!!.y) {
newDirection = DIR_UP
} else {
newDirection = DIR_DOWN
}
} else {
newDirection = DIR_NONE
}
}
if (direction != newDirection) {
isDirectionChanged = true
direction = newDirection
} else {
isDirectionChanged = false
}
}
}
private var topViewHeight: Int = 0
private var bottomViewHeight: Int = 0
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
measureChildren(widthMeasureSpec, heightMeasureSpec)
if (topRefrashView != null) {
topViewHeight = topRefrashView!!.measuredHeight
}
if (bottomView != null) {
bottomViewHeight = bottomView!!.measuredHeight
}
setMeasuredDimension(widthMeasureSpec, heightMeasureSpec)
}
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
if (topRefrashView != null) {
topRefrashView!!.layout(0, -topViewHeight, measuredWidth, 0)
}
if (mainView != null) {
mainView!!.layout(paddingLeft, paddingTop, measuredWidth - paddingRight, measuredHeight - paddingBottom)
}
if (bottomView != null) {
bottomView!!.layout(0, measuredHeight, measuredWidth, measuredHeight + bottomViewHeight)
}
}
override fun onFinishInflate() {
if (childCount > 0) {
for (i in 0..childCount - 1) {
when (getChildAt(i).id) {
-1 -> {
throw IllegalArgumentException("child with NOID is not supported")
}
topRefrashViewId -> {
topRefrashView = getChildAt(i)
}
bottomRefrashViewId -> {
bottomView = getChildAt(i)
}
mainViewId -> {
mainView = getChildAt(i)
}
else -> {
}
}
}
}
super.onFinishInflate()
}
}
interface OnRefrashListener {
fun onTopRefrash()
fun onBottomRefrash()
fun onTopViewMove(transY: Int)
fun onBottomViewMove(transY: Int)
}
abstract class OnRefrashAdapter : OnRefrashListener {
override fun onBottomViewMove(transY: Int) {
}
override fun onTopViewMove(transY: Int) {
}
}
data class MotionEventBean(var type: Int, var x: Float, var y: Float) : Serializable {
fun updateDatas(event: MotionEvent) {
type = event.actionMasked
x = event.x
y = event.y
}
fun updateDatas(event: MotionEventBean) {
type = event.type
x = event.x
y = event.y
}
}
\ No newline at end of file
package com.x.leo.refrashviews;
import android.content.Context;
import android.graphics.Point;
import android.support.v4.widget.ViewDragHelper;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.LinearLayout;
/**
* @作者:XLEO
* @创建日期: 2017/8/25 15:32
* @描述:${TODO}
* @更新者:${Author}$
* @更新时间:${Date}$
* @更新描述:${TODO}
* @下一步:
*/
public class VDHLayout extends LinearLayout
{
private ViewDragHelper mDragger;
private View mDragView;
private View mAutoBackView;
private View mEdgeTrackerView;
private Point mAutoBackOriginPos = new Point();
public VDHLayout(Context context, AttributeSet attrs)
{
super(context, attrs);
mDragger = ViewDragHelper.create(this, 1.0f, new ViewDragHelper.Callback()
{
@Override
public boolean tryCaptureView(View child, int pointerId)
{
//mEdgeTrackerView禁止直接移动
return child == mDragView || child == mAutoBackView;
}
@Override
public int clampViewPositionHorizontal(View child, int left, int dx)
{
return left;
}
@Override
public int clampViewPositionVertical(View child, int top, int dy)
{
return top;
}
//手指释放的时候回调
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel)
{
//mAutoBackView手指释放时可以自动回去
if (releasedChild == mAutoBackView)
{
mDragger.settleCapturedViewAt(mAutoBackOriginPos.x, mAutoBackOriginPos.y);
invalidate();
}
}
//在边界拖动时回调
@Override
public void onEdgeDragStarted(int edgeFlags, int pointerId)
{
mDragger.captureChildView(mEdgeTrackerView, pointerId);
}
});
mDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event)
{
return mDragger.shouldInterceptTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event)
{
mDragger.processTouchEvent(event);
return true;
}
@Override
public void computeScroll()
{
if(mDragger.continueSettling(true))
{
invalidate();
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b)
{
super.onLayout(changed, l, t, r, b);
mAutoBackOriginPos.x = mAutoBackView.getLeft();
mAutoBackOriginPos.y = mAutoBackView.getTop();
}
@Override
protected void onFinishInflate()
{
super.onFinishInflate();
mDragView = getChildAt(0);
mAutoBackView = getChildAt(1);
mEdgeTrackerView = getChildAt(2);
}
}
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="RefreshLayout">
<attr name="topView" format="reference"/>
<attr name="mainView" format="reference"/>
<attr name="bottomView" format="reference"/>
</declare-styleable>
</resources>
<resources>
<string name="app_name">RefrashViews</string>
</resources>
package com.x.leo.refrashviews;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() throws Exception {
assertEquals(4, 2 + 2);
}
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment