diff --git a/README.md b/README.md index 7a01f76c2e526163cfebab25c9f5e8a5a637bffe..bfb8e57e70b235f7a053a333fe1c501f6f128c72 100644 --- a/README.md +++ b/README.md @@ -65,8 +65,6 @@ Authors of the previous version of Quasseldroid: Apache-2.0 * [**Android Architecture Components: Room**](https://android.googlesource.com/platform/frameworks/support/+/master/persistence) Apache-2.0 -* [**Android Sliding Up Panel**](https://github.com/umano/AndroidSlidingUpPanel) - Apache-2.0 * [**Android Support Library**](https://android.googlesource.com/platform/frameworks/support/+/master) Apache-2.0 * [**Android Support Library: Constraint Layout**](https://android.googlesource.com/platform/frameworks/opt/sherpa/+/studio-3.0/constraintlayout) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 9985a8ed0ea7adb4648299f8be62cb46a9fe79e1..0673a83e8d72b1a81a3584b9ada0a2389a28b2ff 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -175,7 +175,6 @@ dependencies { implementation("com.github.bumptech.glide", "recyclerview-integration", version) kapt("com.github.bumptech.glide", "compiler", version) } - implementation(project(":slidingpanel")) // Quality Assurance implementation(project(":malheur")) diff --git a/app/sampledata/libraries.json b/app/sampledata/libraries.json index ef65ab1d1ee485f27e4502da299765daca96fbe8..a4e876054dae44a28fe5c7f1039c695dadab22fb 100644 --- a/app/sampledata/libraries.json +++ b/app/sampledata/libraries.json @@ -36,15 +36,6 @@ }, "url": "https://android.googlesource.com/platform/frameworks/support/+/master/persistence" }, - { - "name": "Android Sliding Up Panel", - "version": "3.5.0", - "license": { - "short_name": "Apache-2.0", - "full_name": "Apache License" - }, - "url": "https://github.com/umano/AndroidSlidingUpPanel" - }, { "name": "Android Support Library", "version": "27.1.1", diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/ChatActivity.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/ChatActivity.kt index c93283df54b2b27af68c84ab0665866baf188dd8..f24be1b2d3fc93bdb3944f95b6b30850d42f9ef7 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/ChatActivity.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/ChatActivity.kt @@ -27,6 +27,7 @@ import android.content.SharedPreferences import android.os.Build import android.os.Bundle import android.os.PersistableBundle +import android.support.design.widget.BottomSheetBehavior import android.support.v4.widget.DrawerLayout import android.support.v7.app.ActionBarDrawerToggle import android.support.v7.widget.DefaultItemAnimator @@ -39,7 +40,6 @@ import android.widget.EditText import butterknife.BindView import butterknife.ButterKnife import com.afollestad.materialdialogs.MaterialDialog -import com.sothree.slidinguppanel.SlidingUpPanelLayout import de.kuschku.libquassel.connection.ConnectionState import de.kuschku.libquassel.connection.QuasselSecurityException import de.kuschku.libquassel.protocol.Buffer_Type @@ -64,6 +64,7 @@ import de.kuschku.quasseldroid.ui.coresettings.CoreSettingsActivity import de.kuschku.quasseldroid.util.helper.* import de.kuschku.quasseldroid.util.irc.format.IrcFormatDeserializer import de.kuschku.quasseldroid.util.service.ServiceBoundActivity +import de.kuschku.quasseldroid.util.ui.DragInterceptBottomSheetBehavior import de.kuschku.quasseldroid.util.ui.MaterialContentLoadingProgressBar import de.kuschku.quasseldroid.viewmodel.data.BufferData import org.threeten.bp.Instant @@ -85,9 +86,6 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc @BindView(R.id.progress_bar) lateinit var progressBar: MaterialContentLoadingProgressBar - @BindView(R.id.editor_panel) - lateinit var editorPanel: SlidingUpPanelLayout - @BindView(R.id.autocomplete_list) lateinit var autoCompleteList: RecyclerView @@ -107,6 +105,8 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc @Named("ui") lateinit var autoCompleteAdapter: AutoCompleteAdapter + lateinit var editorBottomSheet: DragInterceptBottomSheetBehavior<View> + private val dateTimeFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM) private lateinit var drawerToggle: ActionBarDrawerToggle @@ -176,12 +176,16 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc }) if (autoCompleteSettings.prefix || autoCompleteSettings.auto) { + val autoCompleteBottomSheet = BottomSheetBehavior.from(autoCompleteList) chatlineFragment?.let { autoCompleteAdapter.setOnClickListener(it.chatline::autoComplete) autoCompleteList.layoutManager = LinearLayoutManager(it.activity) autoCompleteList.itemAnimator = DefaultItemAnimator() autoCompleteList.adapter = autoCompleteAdapter - it.autoCompleteHelper.setDataListener { + it.autoCompleteHelper.addDataListener { + autoCompleteBottomSheet.state = + if (it.isEmpty()) BottomSheetBehavior.STATE_HIDDEN + else BottomSheetBehavior.STATE_COLLAPSED autoCompleteAdapter.submitList(it) } } @@ -484,8 +488,22 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc onNewIntent(intent) - editorPanel.panelState = SlidingUpPanelLayout.PanelState.COLLAPSED - chatlineFragment?.panelSlideListener?.let(editorPanel::addPanelSlideListener) + editorBottomSheet = DragInterceptBottomSheetBehavior.from(chatlineFragment?.view) + editorBottomSheet.state = BottomSheetBehavior.STATE_COLLAPSED + chatlineFragment?.panelSlideListener?.let(editorBottomSheet::setBottomSheetCallback) + + chatlineFragment?.historyBottomSheet?.setBottomSheetCallback( + object : BottomSheetBehavior.BottomSheetCallback() { + override fun onSlide(bottomSheet: View, slideOffset: Float) { + val opacity = (1.0f - slideOffset) / 2.0f + chatlineFragment?.editorContainer?.alpha = opacity + } + + override fun onStateChanged(bottomSheet: View, newState: Int) { + editorBottomSheet.allowDragging = newState == BottomSheetBehavior.STATE_HIDDEN + } + } + ) } var bufferData: BufferData? = null @@ -627,13 +645,13 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc } override fun onBackPressed() { - if (chatlineFragment?.historyPanel?.panelState == SlidingUpPanelLayout.PanelState.EXPANDED) { - chatlineFragment?.historyPanel?.panelState = SlidingUpPanelLayout.PanelState.COLLAPSED + if (chatlineFragment?.historyBottomSheet?.state == BottomSheetBehavior.STATE_EXPANDED) { + chatlineFragment?.historyBottomSheet?.state = BottomSheetBehavior.STATE_HIDDEN return } - if (editorPanel.panelState == SlidingUpPanelLayout.PanelState.EXPANDED) { - editorPanel.panelState = SlidingUpPanelLayout.PanelState.COLLAPSED + if (editorBottomSheet.state == BottomSheetBehavior.STATE_EXPANDED) { + editorBottomSheet.state = BottomSheetBehavior.STATE_COLLAPSED return } diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/AutoCompleteHelper.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/AutoCompleteHelper.kt index 44a0b934b975b588ee221ceb71fdf47311c3aa3b..814dc343298a24ca3ecfed75f77f1a1ef95dff26 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/AutoCompleteHelper.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/AutoCompleteHelper.kt @@ -50,7 +50,7 @@ class AutoCompleteHelper( private val viewModel: EditorViewModel ) { private var autocompleteListener: ((AutoCompletionState) -> Unit)? = null - private var dataListener: ((List<AutoCompleteItem>) -> Unit)? = null + private var dataListeners: List<((List<AutoCompleteItem>) -> Unit)> = emptyList() var autoCompletionState: AutoCompletionState? = null @@ -72,7 +72,7 @@ class AutoCompleteHelper( (autoCompleteSettings.prefix && query.startsWith('@')) || (autoCompleteSettings.prefix && query.startsWith('#')) val list = if (shouldShowResults) it?.second.orEmpty() else emptyList() - dataListener?.invoke(list.map { + val data = list.map { if (it is AutoCompleteItem.UserItem) { val nickName = it.nick val senderColorIndex = IrcUserUtils.senderColor(nickName) @@ -114,7 +114,10 @@ class AutoCompleteHelper( } else { it } - }) + } + dataListeners.forEach { + it(data) + } }) } @@ -122,8 +125,12 @@ class AutoCompleteHelper( this.autocompleteListener = listener } - fun setDataListener(listener: ((List<AutoCompleteItem>) -> Unit)?) { - this.dataListener = listener + fun addDataListener(listener: ((List<AutoCompleteItem>) -> Unit)) { + this.dataListeners += listener + } + + fun removeDataListener(listener: ((List<AutoCompleteItem>) -> Unit)) { + this.dataListeners -= listener } private fun autoCompleteDataFull(): List<AutoCompleteItem> { diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/ChatlineFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/ChatlineFragment.kt index 232e4b1e2331347bb40f59f4b2b97bbe216620d1..d8a0262edf67d0482cfd26a7753a29d2cde0d574 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/ChatlineFragment.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/ChatlineFragment.kt @@ -21,6 +21,7 @@ package de.kuschku.quasseldroid.ui.chat.input import android.arch.lifecycle.Observer import android.os.Bundle +import android.support.design.widget.BottomSheetBehavior import android.support.v7.widget.AppCompatImageButton import android.support.v7.widget.DefaultItemAnimator import android.support.v7.widget.LinearLayoutManager @@ -31,7 +32,6 @@ import android.view.View import android.view.ViewGroup import butterknife.BindView import butterknife.ButterKnife -import com.sothree.slidinguppanel.SlidingUpPanelLayout import de.kuschku.libquassel.quassel.syncables.interfaces.IAliasManager import de.kuschku.quasseldroid.R import de.kuschku.quasseldroid.settings.AppearanceSettings @@ -60,12 +60,18 @@ class ChatlineFragment : ServiceBoundFragment() { @BindView(R.id.msg_history) lateinit var messageHistory: RecyclerView - @BindView(R.id.history_panel) - lateinit var historyPanel: SlidingUpPanelLayout - @BindView(R.id.autocomplete_list) lateinit var autoCompleteList: RecyclerView + @BindView(R.id.close) + lateinit var close: AppCompatImageButton + + @BindView(R.id.card_panel) + lateinit var cardPanel: View + + @BindView(R.id.editor_container) + lateinit var editorContainer: View + @Inject lateinit var autoCompleteSettings: AutoCompleteSettings @@ -91,13 +97,13 @@ class ChatlineFragment : ServiceBoundFragment() { lateinit var autoCompleteHelper: AutoCompleteHelper - val panelSlideListener = object : SlidingUpPanelLayout.PanelSlideListener { - override fun onPanelSlide(panel: View?, slideOffset: Float) = Unit + lateinit var historyBottomSheet: BottomSheetBehavior<View> + + val panelSlideListener = object : BottomSheetBehavior.BottomSheetCallback() { + override fun onSlide(bottomSheet: View, slideOffset: Float) = Unit - override fun onPanelStateChanged(panel: View?, - previousState: SlidingUpPanelLayout.PanelState?, - newState: SlidingUpPanelLayout.PanelState?) { - editorHelper.setMultiLine(newState == SlidingUpPanelLayout.PanelState.COLLAPSED) + override fun onStateChanged(bottomSheet: View, newState: Int) { + editorHelper.setMultiLine(newState != BottomSheetBehavior.STATE_COLLAPSED) } } @@ -127,22 +133,31 @@ class ChatlineFragment : ServiceBoundFragment() { editorViewModel.lastWord.onNext(editorHelper.lastWord) + val autoCompleteBottomSheet = BottomSheetBehavior.from(autoCompleteList) if (autoCompleteSettings.prefix || autoCompleteSettings.auto) { autoCompleteAdapter.setOnClickListener(chatline::autoComplete) - autoCompleteList.layoutManager = LinearLayoutManager(activity) + autoCompleteList.layoutManager = LinearLayoutManager(context) autoCompleteList.itemAnimator = DefaultItemAnimator() autoCompleteList.adapter = autoCompleteAdapter - autoCompleteHelper.setDataListener { + autoCompleteHelper.addDataListener { + autoCompleteBottomSheet.state = + if (it.isEmpty()) BottomSheetBehavior.STATE_HIDDEN + else BottomSheetBehavior.STATE_COLLAPSED autoCompleteAdapter.submitList(it) } } + historyBottomSheet = BottomSheetBehavior.from(cardPanel) + historyBottomSheet.state = BottomSheetBehavior.STATE_HIDDEN messageHistory.itemAnimator = DefaultItemAnimator() messageHistory.layoutManager = LinearLayoutManager(requireContext()) val messageHistoryAdapter = MessageHistoryAdapter() messageHistoryAdapter.setOnItemClickListener { text -> editorHelper.replaceText(text) - historyPanel.panelState = SlidingUpPanelLayout.PanelState.COLLAPSED + historyBottomSheet.state = BottomSheetBehavior.STATE_HIDDEN + } + close.setOnClickListener { + historyBottomSheet.state = BottomSheetBehavior.STATE_HIDDEN } messageHistory.adapter = messageHistoryAdapter viewModel.recentlySentMessages.toLiveData() @@ -186,7 +201,7 @@ class ChatlineFragment : ServiceBoundFragment() { toolbar.setOnMenuItemClickListener { when (it.itemId) { R.id.action_input_history -> { - historyPanel.panelState = SlidingUpPanelLayout.PanelState.EXPANDED + historyBottomSheet.state = BottomSheetBehavior.STATE_EXPANDED true } else -> false diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/RichEditText.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/RichEditText.kt index a72f0dfd394a5f559346721c907739fdfeffe242..b7340045672b8c44f03e40a28c022b2688f5c33f 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/RichEditText.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/RichEditText.kt @@ -279,9 +279,9 @@ class RichEditText : EditTextSelectionChange { val selectionEnd = selectionEnd inputType = if (enabled) { - inputType and InputType.TYPE_TEXT_FLAG_MULTI_LINE.inv() - } else { inputType or InputType.TYPE_TEXT_FLAG_MULTI_LINE + } else { + inputType and InputType.TYPE_TEXT_FLAG_MULTI_LINE.inv() } setSelection(selectionStart, selectionEnd) diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/topic/TopicFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/topic/TopicFragment.kt index 8d5a21025f5946b9318c7857eddd78ea73440289..6efb38992654dd8675a34ed3ad0ff8d26623f76c 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/topic/TopicFragment.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/topic/TopicFragment.kt @@ -106,7 +106,7 @@ class TopicFragment : SettingsFragment(), SettingsFragment.Savable { autoCompleteList.layoutManager = LinearLayoutManager(activity) autoCompleteList.itemAnimator = DefaultItemAnimator() autoCompleteList.adapter = autoCompleteAdapter - autoCompleteHelper.setDataListener { + autoCompleteHelper.addDataListener { autoCompleteAdapter.submitList(it) } } diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/about/AboutFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/about/AboutFragment.kt index 9c0315ce3a5bedee0e623a2baaa3ff1ee2344b88..86bd2b5d051180d55a545f037eab438de0a58dd1 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/about/AboutFragment.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/about/AboutFragment.kt @@ -114,12 +114,6 @@ class AboutFragment : DaggerFragment() { license = apache2, url = "https://android.googlesource.com/platform/frameworks/support/+/master/persistence" ), - Library( - name = "Android Sliding Up Panel", - version = "3.5.0", - license = apache2, - url = "https://github.com/umano/AndroidSlidingUpPanel" - ), Library( name = "Android Support Library", version = "27.1.1", diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/aliasitem/AliasItemFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/aliasitem/AliasItemFragment.kt index dd5b2bc89b78b12dbe8109dc0aece029b338dabc..e1e7cd6b36626827ea8119a625d2a60703713365 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/aliasitem/AliasItemFragment.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/aliasitem/AliasItemFragment.kt @@ -119,7 +119,7 @@ class AliasItemFragment : SettingsFragment(), SettingsFragment.Savable, autoCompleteList.layoutManager = LinearLayoutManager(activity) autoCompleteList.itemAnimator = DefaultItemAnimator() autoCompleteList.adapter = autoCompleteAdapter - autoCompleteHelper.setDataListener { + autoCompleteHelper.addDataListener { autoCompleteAdapter.submitList(it) } } diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/ui/DragInterceptBottomSheetBehavior.kt b/app/src/main/java/de/kuschku/quasseldroid/util/ui/DragInterceptBottomSheetBehavior.kt new file mode 100644 index 0000000000000000000000000000000000000000..0234f9c01be780b6096a07b2e28aa680a17141e4 --- /dev/null +++ b/app/src/main/java/de/kuschku/quasseldroid/util/ui/DragInterceptBottomSheetBehavior.kt @@ -0,0 +1,56 @@ +/* + * Quasseldroid - Quassel client for Android + * + * Copyright (c) 2018 Janne Koschinski + * Copyright (c) 2018 The Quassel Project + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package de.kuschku.quasseldroid.util.ui + +import android.content.Context +import android.support.design.widget.BottomSheetBehavior +import android.support.design.widget.CoordinatorLayout +import android.util.AttributeSet +import android.view.MotionEvent +import android.view.View + +class DragInterceptBottomSheetBehavior<V : View> : BottomSheetBehavior<V> { + constructor() : super() + constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) + + var allowDragging = true + + override fun onInterceptTouchEvent(parent: CoordinatorLayout?, child: V, + event: MotionEvent?): Boolean { + if (!allowDragging) return false + return super.onInterceptTouchEvent(parent, child, event) + } + + companion object { + /** + * A utility function to get the [BottomSheetBehavior] associated with the `view`. + * + * @param view The [View] with [BottomSheetBehavior]. + * @return The [BottomSheetBehavior] associated with the `view`. + */ + fun <V : View> from(view: V?): DragInterceptBottomSheetBehavior<V> { + val params = view?.layoutParams as? CoordinatorLayout.LayoutParams + ?: throw IllegalArgumentException("The view is not a child of CoordinatorLayout") + val behavior = params.behavior as? DragInterceptBottomSheetBehavior<*> + ?: throw IllegalArgumentException("The view is not associated with BottomSheetBehavior") + return behavior as DragInterceptBottomSheetBehavior<V> + } + } +} diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/ui/ShadowView.java b/app/src/main/java/de/kuschku/quasseldroid/util/ui/ShadowView.java new file mode 100644 index 0000000000000000000000000000000000000000..3b4389bfbab402ba87f6d24c31776042b2021191 --- /dev/null +++ b/app/src/main/java/de/kuschku/quasseldroid/util/ui/ShadowView.java @@ -0,0 +1,186 @@ +/* + * Copyright 2014 Google Inc. + * + * 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. + */ + +package de.kuschku.quasseldroid.util.ui; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Color; +import android.graphics.LinearGradient; +import android.graphics.Shader; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.PaintDrawable; +import android.graphics.drawable.ShapeDrawable; +import android.graphics.drawable.shapes.RectShape; +import android.os.Build; +import android.support.annotation.Nullable; +import android.support.annotation.RequiresApi; +import android.util.AttributeSet; +import android.util.LruCache; +import android.view.Gravity; +import android.view.View; + +import de.kuschku.quasseldroid.R; + +/** + * Shadow view based on the {@code ScrimUtil.java} class from the Muzei App. Take a look at + * <a href="https://plus.google.com/+RomanNurik/posts/2QvHVFWrHZf">this post</a> from Roman + * Nurik for more details. Find the source code + * <a href="https://github.com/romannurik/muzei/blob/master/main/src/main/java/com/google/android/apps/muzei/util/ScrimUtil.java">here</a>. + */ +public class ShadowView extends View { + + private static final LruCache<Integer, Drawable> cubicGradientScrimCache = new LruCache<>(10); + + public ShadowView(Context context) { + super(context); + initialize(context, null, 0, 0); + } + + public ShadowView(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + initialize(context, attrs, 0, 0); + } + + public ShadowView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + initialize(context, attrs, defStyleAttr, 0); + } + + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public ShadowView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + initialize(context, attrs, defStyleAttr, defStyleRes); + } + + /** + * Initializes the view. + * + * @param context The Context the view is running in, through which it can + * access the current theme, resources, etc. + * @param attrs The attributes of the XML tag that is inflating the view. + * @param defStyleAttr An attribute in the current theme that contains a + * reference to a style resource that supplies default values for + * the view. Can be 0 to not look for defaults. + * @param defStyleRes A resource identifier of a style resource that + * supplies default values for the view, used only if + * defStyleAttr is 0 or can not be found in the theme. Can be 0 + * to not look for defaults. + * @see View(Context, AttributeSet, int) + */ + private void initialize(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + int gravity = Gravity.TOP; + + // Get the attributes. + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ShadowView, defStyleAttr, defStyleRes); + try { + if (a != null) { + gravity = a.getInt(R.styleable.ShadowView_android_gravity, gravity); + } + + } finally { + if (a != null) a.recycle(); + } + + // Set the gradient as background. + setBackground(makeCubicGradientScrimDrawable(0x44000000, 8, gravity)); + } + + /** + * Creates an approximated cubic gradient using a multi-stop linear gradient. + */ + private Drawable makeCubicGradientScrimDrawable(int baseColor, int numStops, int gravity) { + + // Generate a cache key by hashing together the inputs, based on the method described in the Effective Java book + int cacheKeyHash = baseColor; + cacheKeyHash = 31 * cacheKeyHash + numStops; + cacheKeyHash = 31 * cacheKeyHash + gravity; + + Drawable cachedGradient = cubicGradientScrimCache.get(cacheKeyHash); + if (cachedGradient != null) { + return cachedGradient; + } + + numStops = Math.max(numStops, 2); + + PaintDrawable paintDrawable = new PaintDrawable(); + paintDrawable.setShape(new RectShape()); + + final int[] stopColors = new int[numStops]; + + int red = Color.red(baseColor); + int green = Color.green(baseColor); + int blue = Color.blue(baseColor); + int alpha = Color.alpha(baseColor); + + for (int i = 0; i < numStops; i++) { + float x = i * 1f / (numStops - 1); + float opacity = constrain(0, 1, (float) Math.pow(x, 3)); + stopColors[i] = Color.argb((int) (alpha * opacity), red, green, blue); + } + + final float x0, x1, y0, y1; + switch (gravity & Gravity.HORIZONTAL_GRAVITY_MASK) { + case Gravity.LEFT: + x0 = 1; + x1 = 0; + break; + case Gravity.RIGHT: + x0 = 0; + x1 = 1; + break; + default: + x0 = 0; + x1 = 0; + break; + } + switch (gravity & Gravity.VERTICAL_GRAVITY_MASK) { + case Gravity.TOP: + y0 = 1; + y1 = 0; + break; + case Gravity.BOTTOM: + y0 = 0; + y1 = 1; + break; + default: + y0 = 0; + y1 = 0; + break; + } + + paintDrawable.setShaderFactory(new ShapeDrawable.ShaderFactory() { + @Override + public Shader resize(int width, int height) { + return new LinearGradient( + width * x0, + height * y0, + width * x1, + height * y1, + stopColors, null, + Shader.TileMode.CLAMP); + } + }); + + cubicGradientScrimCache.put(cacheKeyHash, paintDrawable); + return paintDrawable; + } + + private float constrain(float min, float max, float v) { + return Math.max(min, Math.min(max, v)); + } + +} diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/ui/TouchInterceptingFrameLayout.kt b/app/src/main/java/de/kuschku/quasseldroid/util/ui/TouchInterceptingFrameLayout.kt new file mode 100644 index 0000000000000000000000000000000000000000..0d9731185965e64bf3ec36253730bf82b468d958 --- /dev/null +++ b/app/src/main/java/de/kuschku/quasseldroid/util/ui/TouchInterceptingFrameLayout.kt @@ -0,0 +1,38 @@ +/* + * Quasseldroid - Quassel client for Android + * + * Copyright (c) 2018 Janne Koschinski + * Copyright (c) 2018 The Quassel Project + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package de.kuschku.quasseldroid.util.ui + +import android.annotation.SuppressLint +import android.content.Context +import android.util.AttributeSet +import android.view.MotionEvent +import android.widget.FrameLayout + +class TouchInterceptingFrameLayout : FrameLayout { + constructor(context: Context?) : super(context) + constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) + constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : + super(context, attrs, defStyleAttr) + + @SuppressLint("ClickableViewAccessibility") + override fun onTouchEvent(event: MotionEvent): Boolean { + return true + } +} diff --git a/app/src/main/res/layout-land/layout_main.xml b/app/src/main/res/layout-land/layout_main.xml index d201b88f2385921c8452be14f403fe46760739d3..6ca37163de492e40800b1b6f980d4773afc5c38e 100644 --- a/app/src/main/res/layout-land/layout_main.xml +++ b/app/src/main/res/layout-land/layout_main.xml @@ -17,47 +17,59 @@ with this program. If not, see <http://www.gnu.org/licenses/>. --> -<com.sothree.slidinguppanel.SlidingUpPanelLayout xmlns:android="http://schemas.android.com/apk/res/android" +<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" - android:id="@+id/editor_panel" android:layout_width="match_parent" android:layout_height="match_parent" - android:gravity="bottom" - app:umanoAntiDragView="@id/card_panel" - app:umanoPanelHeight="?actionBarSize" - app:umanoScrollableView="@id/chatline_scroller" - app:umanoShadowHeight="4dp"> + android:gravity="bottom"> - <LinearLayout + <include layout="@layout/layout_toolbar" /> + + <android.support.design.widget.CoordinatorLayout android:layout_width="match_parent" android:layout_height="match_parent" - android:fitsSystemWindows="true" + android:layout_marginBottom="?actionBarSize" android:orientation="vertical"> - <include layout="@layout/layout_toolbar" /> - <fragment android:id="@+id/fragment_messages" android:name="de.kuschku.quasseldroid.ui.chat.messages.MessageListFragment" android:layout_width="match_parent" - android:layout_height="0dip" - android:layout_weight="1" + android:layout_height="match_parent" tools:layout="@layout/fragment_messages" /> - <de.kuschku.quasseldroid.util.ui.AutoCompleteRecyclerView + <de.kuschku.quasseldroid.util.ui.ShadowView + android:layout_width="match_parent" + android:layout_height="16dp" + android:gravity="bottom" + app:layout_anchor="@id/autocomplete_list" /> + + <android.support.v7.widget.RecyclerView android:id="@+id/autocomplete_list" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="?colorBackgroundCard" + app:behavior_hideable="true" + app:behavior_peekHeight="92dp" + app:layout_behavior="@string/bottom_sheet_behavior" tools:listitem="@layout/widget_nick" /> - </LinearLayout> + </android.support.design.widget.CoordinatorLayout> + + <de.kuschku.quasseldroid.util.ui.ShadowView + android:layout_width="match_parent" + android:layout_height="16dp" + android:gravity="bottom" + app:layout_anchor="@id/fragment_chatline" /> <fragment android:id="@+id/fragment_chatline" android:name="de.kuschku.quasseldroid.ui.chat.input.ChatlineFragment" android:layout_width="match_parent" android:layout_height="match_parent" + app:behavior_hideable="false" + app:behavior_peekHeight="?actionBarSize" + app:layout_behavior="@string/drag_intercept_bottom_sheet_behavior" tools:layout="@layout/fragment_chatline" /> -</com.sothree.slidinguppanel.SlidingUpPanelLayout> +</android.support.design.widget.CoordinatorLayout> diff --git a/app/src/main/res/layout-sw600dp-land/layout_main.xml b/app/src/main/res/layout-sw600dp-land/layout_main.xml index 287a799faf38e444528ac6c57d7f9ac7d60a7420..4cc02bcede54a5d194d2bf9d5d3700939e7f313a 100644 --- a/app/src/main/res/layout-sw600dp-land/layout_main.xml +++ b/app/src/main/res/layout-sw600dp-land/layout_main.xml @@ -27,45 +27,51 @@ <include layout="@layout/layout_toolbar" /> - <com.sothree.slidinguppanel.SlidingUpPanelLayout - android:id="@+id/editor_panel" + <android.support.design.widget.CoordinatorLayout android:layout_width="match_parent" android:layout_height="match_parent" - android:gravity="bottom" - app:umanoAntiDragView="@id/card_panel" - app:umanoPanelHeight="?actionBarSize" - app:umanoScrollableView="@id/chatline_scroller" - app:umanoShadowHeight="4dp"> + android:gravity="bottom"> - <LinearLayout + <android.support.design.widget.CoordinatorLayout android:layout_width="match_parent" android:layout_height="match_parent" + android:layout_marginBottom="?actionBarSize" android:orientation="vertical"> <fragment android:id="@+id/fragment_messages" android:name="de.kuschku.quasseldroid.ui.chat.messages.MessageListFragment" android:layout_width="match_parent" - android:layout_height="0dip" - android:layout_weight="1" + android:layout_height="match_parent" tools:layout="@layout/fragment_messages" /> - <de.kuschku.quasseldroid.util.ui.AutoCompleteRecyclerView + <android.support.v7.widget.RecyclerView android:id="@+id/autocomplete_list" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="?colorBackgroundCard" + app:behavior_hideable="true" + app:behavior_peekHeight="92dp" + app:layout_behavior="@string/bottom_sheet_behavior" tools:listitem="@layout/widget_nick" /> + </android.support.design.widget.CoordinatorLayout> - </LinearLayout> + <de.kuschku.quasseldroid.util.ui.ShadowView + android:layout_width="match_parent" + android:layout_height="16dp" + android:gravity="bottom" + app:layout_anchor="@id/fragment_chatline" /> <fragment android:id="@+id/fragment_chatline" android:name="de.kuschku.quasseldroid.ui.chat.input.ChatlineFragment" android:layout_width="match_parent" android:layout_height="match_parent" + app:behavior_hideable="false" + app:behavior_peekHeight="?actionBarSize" + app:layout_behavior="@string/drag_intercept_bottom_sheet_behavior" tools:layout="@layout/fragment_chatline" /> - </com.sothree.slidinguppanel.SlidingUpPanelLayout> + </android.support.design.widget.CoordinatorLayout> </LinearLayout> diff --git a/app/src/main/res/layout/fragment_chatline.xml b/app/src/main/res/layout/fragment_chatline.xml index fefc8781ac046ee10656453c4d0244a2386d3780..f79e200106066e23aecce127119b735960a0c0b4 100644 --- a/app/src/main/res/layout/fragment_chatline.xml +++ b/app/src/main/res/layout/fragment_chatline.xml @@ -17,20 +17,15 @@ with this program. If not, see <http://www.gnu.org/licenses/>. --> -<com.sothree.slidinguppanel.SlidingUpPanelLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" +<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/history_panel" android:layout_width="match_parent" android:layout_height="match_parent" - android:gravity="bottom" - app:umanoFadeColor="?colorBackground" - app:umanoOverlay="true" - app:umanoPanelHeight="0dip" - app:umanoScrollableView="@id/msg_history" - app:umanoShadowHeight="0dip"> + android:background="?colorBackground" + android:gravity="bottom"> <include layout="@layout/layout_editor" /> <include layout="@layout/layout_history" /> -</com.sothree.slidinguppanel.SlidingUpPanelLayout> +</android.support.design.widget.CoordinatorLayout> diff --git a/app/src/main/res/layout/layout_editor.xml b/app/src/main/res/layout/layout_editor.xml index 44e0fc4ce4ca840b61e56c6c38f82be01dddba99..41d4276d0275ea46e881801f60189ee15253dc2a 100644 --- a/app/src/main/res/layout/layout_editor.xml +++ b/app/src/main/res/layout/layout_editor.xml @@ -17,75 +17,92 @@ with this program. If not, see <http://www.gnu.org/licenses/>. --> -<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/editor_container" android:layout_width="match_parent" - android:layout_height="match_parent"> + android:layout_height="match_parent" + android:orientation="vertical"> - <android.support.v7.widget.AppCompatImageButton - android:id="@+id/tab_complete" - style="?attr/buttonStyleSmall" - android:layout_width="?attr/actionBarSize" - android:layout_height="?attr/actionBarSize" - android:layout_gravity="top" - android:background="?attr/selectableItemBackgroundBorderless" - android:padding="12dp" - android:scaleType="fitXY" - app:layout_constraintStart_toStartOf="parent" - app:srcCompat="@drawable/ic_tab" - app:tint="?attr/colorTextSecondary" /> - - <ScrollView - android:id="@+id/chatline_scroller" - android:layout_width="0dp" - android:layout_height="0dp" - app:layout_constraintBottom_toTopOf="@+id/autocomplete_list" - app:layout_constraintEnd_toStartOf="@+id/send" - app:layout_constraintHorizontal_bias="1.0" - app:layout_constraintStart_toEndOf="@+id/tab_complete" - app:layout_constraintTop_toTopOf="parent"> + <android.support.design.widget.CoordinatorLayout + android:layout_width="match_parent" + android:layout_height="0dip" + android:layout_weight="1" + android:orientation="vertical"> - <de.kuschku.quasseldroid.ui.chat.input.RichEditText - android:id="@+id/chatline" - style="@style/Widget.RtlConformTextView" + <android.support.constraint.ConstraintLayout android:layout_width="match_parent" - android:layout_height="wrap_content" - android:background="@android:color/transparent" - android:gravity="center_vertical" - android:hint="@string/label_placeholder_message" - android:imeOptions="flagNoExtractUi" - android:inputType="textCapSentences|textAutoCorrect|textShortMessage" - android:minHeight="?attr/actionBarSize" - android:paddingBottom="8dp" - android:paddingLeft="20dp" - android:paddingRight="20dp" - android:paddingTop="8dp" - android:textColor="?attr/colorForeground" - android:textSize="16sp" /> - </ScrollView> + android:layout_height="match_parent"> - <android.support.v7.widget.AppCompatImageButton - android:id="@+id/send" - style="?attr/buttonStyleSmall" - android:layout_width="?attr/actionBarSize" - android:layout_height="?attr/actionBarSize" - android:layout_gravity="top" - android:autoMirrored="true" - android:background="?attr/selectableItemBackgroundBorderless" - android:padding="12dp" - android:scaleType="fitXY" - app:layout_constraintEnd_toEndOf="parent" - app:srcCompat="@drawable/ic_send" - app:tint="?attr/colorAccent" - tools:ignore="UnusedAttribute" /> + <android.support.v7.widget.AppCompatImageButton + android:id="@+id/tab_complete" + style="?attr/buttonStyleSmall" + android:layout_width="?attr/actionBarSize" + android:layout_height="?attr/actionBarSize" + android:layout_gravity="top" + android:background="?attr/selectableItemBackgroundBorderless" + android:padding="12dp" + android:scaleType="fitXY" + app:layout_constraintStart_toStartOf="parent" + app:srcCompat="@drawable/ic_tab" + app:tint="?attr/colorTextSecondary" /> - <de.kuschku.quasseldroid.util.ui.AutoCompleteRecyclerView - android:id="@+id/autocomplete_list" - android:layout_width="match_parent" - android:layout_height="wrap_content" - app:layout_constraintBottom_toTopOf="@+id/formatting_toolbar_container" - tools:listitem="@layout/widget_nick" /> + <ScrollView + android:id="@+id/chatline_scroller" + android:layout_width="0dp" + android:layout_height="0dp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toStartOf="@+id/send" + app:layout_constraintHorizontal_bias="1.0" + app:layout_constraintStart_toEndOf="@+id/tab_complete" + app:layout_constraintTop_toTopOf="parent"> + + <de.kuschku.quasseldroid.ui.chat.input.RichEditText + android:id="@+id/chatline" + style="@style/Widget.RtlConformTextView" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="@android:color/transparent" + android:gravity="center_vertical" + android:hint="@string/label_placeholder_message" + android:imeOptions="flagNoExtractUi" + android:inputType="textCapSentences|textAutoCorrect|textShortMessage" + android:minHeight="?attr/actionBarSize" + android:paddingBottom="8dp" + android:paddingLeft="20dp" + android:paddingRight="20dp" + android:paddingTop="8dp" + android:textColor="?attr/colorForeground" + android:textSize="16sp" /> + </ScrollView> + + <android.support.v7.widget.AppCompatImageButton + android:id="@+id/send" + style="?attr/buttonStyleSmall" + android:layout_width="?attr/actionBarSize" + android:layout_height="?attr/actionBarSize" + android:layout_gravity="top" + android:autoMirrored="true" + android:background="?attr/selectableItemBackgroundBorderless" + android:padding="12dp" + android:scaleType="fitXY" + app:layout_constraintEnd_toEndOf="parent" + app:srcCompat="@drawable/ic_send" + app:tint="?attr/colorAccent" + tools:ignore="UnusedAttribute" /> + </android.support.constraint.ConstraintLayout> + + <android.support.v7.widget.RecyclerView + android:id="@+id/autocomplete_list" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="?colorBackgroundCard" + app:behavior_hideable="true" + app:behavior_peekHeight="@dimen/autocomplete_max_height" + app:layout_behavior="@string/bottom_sheet_behavior" + tools:listitem="@layout/widget_nick" /> + </android.support.design.widget.CoordinatorLayout> <android.support.design.widget.AppBarLayout android:id="@+id/formatting_toolbar_container" @@ -100,4 +117,4 @@ android:layout_height="?attr/actionBarSize" app:contentInsetStart="0dip" /> </android.support.design.widget.AppBarLayout> -</android.support.constraint.ConstraintLayout> +</LinearLayout> diff --git a/app/src/main/res/layout/layout_history.xml b/app/src/main/res/layout/layout_history.xml index 8d00232bf0cfb5b9fcb2d678fd36078e1bc511e6..02f37ca2804dbdd76898f2336cfe7f8383eeb50f 100644 --- a/app/src/main/res/layout/layout_history.xml +++ b/app/src/main/res/layout/layout_history.xml @@ -17,13 +17,16 @@ with this program. If not, see <http://www.gnu.org/licenses/>. --> -<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" +<de.kuschku.quasseldroid.util.ui.TouchInterceptingFrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/card_panel" android:layout_width="match_parent" android:layout_height="match_parent" - tools:ignore="MergeRootFrame"> + app:behavior_hideable="true" + app:behavior_skipCollapsed="true" + app:layout_behavior="@string/bottom_sheet_behavior" + tools:ignore="KeyboardInaccessibleWidget"> <android.support.v7.widget.CardView style="?attr/cardStyle" @@ -61,7 +64,7 @@ android:layout_height="match_parent" android:layout_weight="1" /> - <android.support.v7.widget.AppCompatImageView + <android.support.v7.widget.AppCompatImageButton android:id="@+id/close" android:layout_width="48dp" android:layout_height="48dp" @@ -69,7 +72,7 @@ android:background="?attr/selectableItemBackgroundBorderless" android:padding="12dp" android:scaleType="fitXY" - app:srcCompat="@drawable/ic_close" + app:srcCompat="@drawable/ic_chevron_down" app:tint="?attr/colorForegroundSecondary" /> </LinearLayout> @@ -80,4 +83,4 @@ tools:listitem="@layout/widget_history_message" /> </LinearLayout> </android.support.v7.widget.CardView> -</FrameLayout> +</de.kuschku.quasseldroid.util.ui.TouchInterceptingFrameLayout> diff --git a/app/src/main/res/layout/layout_main.xml b/app/src/main/res/layout/layout_main.xml index af2067d7cacbc6bfcce49e7b705b5b6bdef67756..542176b9e17df63336f2d36f356d283b91354790 100644 --- a/app/src/main/res/layout/layout_main.xml +++ b/app/src/main/res/layout/layout_main.xml @@ -27,44 +27,51 @@ <include layout="@layout/layout_toolbar" /> - <com.sothree.slidinguppanel.SlidingUpPanelLayout - android:id="@+id/editor_panel" + <android.support.design.widget.CoordinatorLayout android:layout_width="match_parent" android:layout_height="match_parent" - android:gravity="bottom" - app:umanoAntiDragView="@id/card_panel" - app:umanoPanelHeight="?actionBarSize" - app:umanoScrollableView="@id/chatline_scroller" - app:umanoShadowHeight="4dp"> + android:gravity="bottom"> - <LinearLayout + <android.support.design.widget.CoordinatorLayout android:layout_width="match_parent" android:layout_height="match_parent" + android:layout_marginBottom="?actionBarSize" android:orientation="vertical"> <fragment android:id="@+id/fragment_messages" android:name="de.kuschku.quasseldroid.ui.chat.messages.MessageListFragment" android:layout_width="match_parent" - android:layout_height="0dip" - android:layout_weight="1" + android:layout_height="match_parent" tools:layout="@layout/fragment_messages" /> - <de.kuschku.quasseldroid.util.ui.AutoCompleteRecyclerView + <android.support.v7.widget.RecyclerView android:id="@+id/autocomplete_list" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="?colorBackgroundCard" + app:behavior_hideable="true" + app:behavior_peekHeight="@dimen/autocomplete_max_height" + app:layout_behavior="@string/bottom_sheet_behavior" tools:listitem="@layout/widget_nick" /> - </LinearLayout> + </android.support.design.widget.CoordinatorLayout> + + <de.kuschku.quasseldroid.util.ui.ShadowView + android:layout_width="match_parent" + android:layout_height="16dp" + android:gravity="bottom" + app:layout_anchor="@id/fragment_chatline" /> <fragment android:id="@+id/fragment_chatline" android:name="de.kuschku.quasseldroid.ui.chat.input.ChatlineFragment" android:layout_width="match_parent" android:layout_height="match_parent" + app:behavior_hideable="false" + app:behavior_peekHeight="?actionBarSize" + app:layout_behavior="@string/drag_intercept_bottom_sheet_behavior" tools:layout="@layout/fragment_chatline" /> - </com.sothree.slidinguppanel.SlidingUpPanelLayout> + </android.support.design.widget.CoordinatorLayout> </LinearLayout> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9399e88620ae8b56e7d7a6d13a1a2a0bd4fc0a06..25530b8c4d291eb330d61c0bb5b3965e5d03b74b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -113,4 +113,6 @@ <string name="delete_confirmation">Are you sure you want to delete this permanently? This can not be undone.</string> <string name="cancel_confirmation">You have unsaved changes. Do you wish to discard them?</string> + + <string name="drag_intercept_bottom_sheet_behavior" translatable="false">de.kuschku.quasseldroid.util.ui.DragInterceptBottomSheetBehavior</string> </resources> diff --git a/app/src/main/res/values/styles_widgets.xml b/app/src/main/res/values/styles_widgets.xml index 4f456518a85a3f1f597ef59cbee8fd782e7bc648..202296289048a85b49652435c4cb6f1c1ae0cd0b 100644 --- a/app/src/main/res/values/styles_widgets.xml +++ b/app/src/main/res/values/styles_widgets.xml @@ -275,6 +275,10 @@ <item name="android:textStyle">bold</item> </style> + <style name="Widget.DrawerRecyclerView" parent=""> + <item name="insetBackground">#4000</item> + </style> + <!-- NavigationDrawerLayout --> <declare-styleable name="NavigationDrawerLayout"> <attr name="insetBackground" /> @@ -289,7 +293,8 @@ <attr name="insetBackground" /> </declare-styleable> - <style name="Widget.DrawerRecyclerView" parent=""> - <item name="insetBackground">#4000</item> - </style> + <!-- ShadowView --> + <declare-styleable name="ShadowView"> + <attr name="android:gravity" /> + </declare-styleable> </resources> diff --git a/settings.gradle b/settings.gradle index 4377405dd0bd8b0d1e65ec15d1f02929c24e394f..eba2c7c83d29b370f8ef4e6730edf7ff248c253a 100644 --- a/settings.gradle +++ b/settings.gradle @@ -22,7 +22,6 @@ include ':invokerannotations', ':lib', ":viewmodel", ":persistence", - ":slidingpanel", ':malheur', ':app' diff --git a/slidingpanel/build.gradle.kts b/slidingpanel/build.gradle.kts deleted file mode 100644 index 273bf00431e7c6211c7efee5536e761a413f4420..0000000000000000000000000000000000000000 --- a/slidingpanel/build.gradle.kts +++ /dev/null @@ -1,32 +0,0 @@ -plugins { - id("com.android.library") -} - -android { - compileSdkVersion(27) - buildToolsVersion("27.0.3") - - defaultConfig { - minSdkVersion(14) - targetSdkVersion(27) - - // Disable test runner analytics - testInstrumentationRunnerArguments = mapOf( - "disableAnalytics" to "true" - ) - } - - lintOptions { - isWarningsAsErrors = true - setLintConfig(file("../lint.xml")) - } -} - -dependencies { - // App Compat - withVersion("27.1.1") { - implementation("com.android.support", "support-v4", version) - implementation("com.android.support", "support-annotations", version) - implementation("com.android.support", "recyclerview-v7", version) - } -} diff --git a/slidingpanel/src/main/AndroidManifest.xml b/slidingpanel/src/main/AndroidManifest.xml deleted file mode 100644 index 351ee1205e79c7330f056adc340613f5ecdd723e..0000000000000000000000000000000000000000 --- a/slidingpanel/src/main/AndroidManifest.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?><!-- - Quasseldroid - Quassel client for Android - - Copyright (c) 2018 Janne Koschinski - Copyright (c) 2018 The Quassel Project - - This program is free software: you can redistribute it and/or modify it - under the terms of the GNU General Public License version 3 as published - by the Free Software Foundation. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program. If not, see <http://www.gnu.org/licenses/>. - --> - -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.sothree.slidinguppanel.library" - android:versionCode="17" - android:versionName="3.4.0" /> diff --git a/slidingpanel/src/main/java/com/sothree/slidinguppanel/ScrollableViewHelper.java b/slidingpanel/src/main/java/com/sothree/slidinguppanel/ScrollableViewHelper.java deleted file mode 100644 index e5aff37731a6f275edd356ada1a124475b94e140..0000000000000000000000000000000000000000 --- a/slidingpanel/src/main/java/com/sothree/slidinguppanel/ScrollableViewHelper.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Quasseldroid - Quassel client for Android - * - * Copyright (c) 2018 Janne Koschinski - * Copyright (c) 2018 The Quassel Project - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 3 as published - * by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -package com.sothree.slidinguppanel; - -import android.support.v7.widget.RecyclerView; -import android.view.View; -import android.widget.ListView; -import android.widget.ScrollView; - -/** - * Helper class for determining the current scroll positions for scrollable views. Currently works - * for ListView, ScrollView and RecyclerView, but the library users can override it to add support - * for other views. - */ -public class ScrollableViewHelper { - /** - * Returns the current scroll position of the scrollable view. If this method returns zero or - * less, it means at the scrollable view is in a position such as the panel should handle - * scrolling. If the method returns anything above zero, then the panel will let the scrollable - * view handle the scrolling - * - * @param scrollableView the scrollable view - * @param isSlidingUp whether or not the panel is sliding up or down - * @return the scroll position - */ - public int getScrollableViewScrollPosition(View scrollableView, boolean isSlidingUp) { - if (scrollableView == null) return 0; - if (scrollableView instanceof ScrollView) { - if (isSlidingUp) { - return scrollableView.getScrollY(); - } else { - ScrollView sv = ((ScrollView) scrollableView); - View child = sv.getChildAt(0); - return (child.getBottom() - (sv.getHeight() + sv.getScrollY())); - } - } else if (scrollableView instanceof ListView && ((ListView) scrollableView).getChildCount() > 0) { - ListView lv = ((ListView) scrollableView); - if (lv.getAdapter() == null) return 0; - if (isSlidingUp) { - View firstChild = lv.getChildAt(0); - // Approximate the scroll position based on the top child and the first visible item - return lv.getFirstVisiblePosition() * firstChild.getHeight() - firstChild.getTop(); - } else { - View lastChild = lv.getChildAt(lv.getChildCount() - 1); - // Approximate the scroll position based on the bottom child and the last visible item - return (lv.getAdapter().getCount() - lv.getLastVisiblePosition() - 1) * lastChild.getHeight() + lastChild.getBottom() - lv.getBottom(); - } - } else if (scrollableView instanceof RecyclerView && ((RecyclerView) scrollableView).getChildCount() > 0) { - RecyclerView rv = ((RecyclerView) scrollableView); - RecyclerView.LayoutManager lm = rv.getLayoutManager(); - if (rv.getAdapter() == null) return 0; - if (isSlidingUp) { - View firstChild = rv.getChildAt(0); - // Approximate the scroll position based on the top child and the first visible item - return rv.getChildLayoutPosition(firstChild) * lm.getDecoratedMeasuredHeight(firstChild) - lm.getDecoratedTop(firstChild); - } else { - View lastChild = rv.getChildAt(rv.getChildCount() - 1); - // Approximate the scroll position based on the bottom child and the last visible item - return (rv.getAdapter().getItemCount() - 1) * lm.getDecoratedMeasuredHeight(lastChild) + lm.getDecoratedBottom(lastChild) - rv.getBottom(); - } - } else { - return 0; - } - } -} diff --git a/slidingpanel/src/main/java/com/sothree/slidinguppanel/SlidingUpPanelLayout.java b/slidingpanel/src/main/java/com/sothree/slidinguppanel/SlidingUpPanelLayout.java deleted file mode 100644 index dd9006a69b791abb579fa452509f7e067066ad8c..0000000000000000000000000000000000000000 --- a/slidingpanel/src/main/java/com/sothree/slidinguppanel/SlidingUpPanelLayout.java +++ /dev/null @@ -1,1506 +0,0 @@ -/* - * Quasseldroid - Quassel client for Android - * - * Copyright (c) 2018 Janne Koschinski - * Copyright (c) 2018 The Quassel Project - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 3 as published - * by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -package com.sothree.slidinguppanel; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.PixelFormat; -import android.graphics.Rect; -import android.graphics.drawable.Drawable; -import android.os.Bundle; -import android.os.Parcelable; -import android.support.v4.view.MotionEventCompat; -import android.support.v4.view.ViewCompat; -import android.util.AttributeSet; -import android.util.Log; -import android.view.Gravity; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.view.accessibility.AccessibilityEvent; -import android.view.animation.AnimationUtils; -import android.view.animation.Interpolator; - -import com.sothree.slidinguppanel.library.R; - -import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; - -public class SlidingUpPanelLayout extends ViewGroup { - - /** - * Tag for the sliding state stored inside the bundle - */ - public static final String SLIDING_STATE = "sliding_state"; - private static final String TAG = SlidingUpPanelLayout.class.getSimpleName(); - /** - * Default peeking out panel height - */ - private static final int DEFAULT_PANEL_HEIGHT = 68; // dp; - /** - * Default anchor point height - */ - private static final float DEFAULT_ANCHOR_POINT = 1.0f; // In relative % - /** - * Default height of the shadow above the peeking out panel - */ - private static final int DEFAULT_SHADOW_HEIGHT = 4; // dp; - - /** - * If no fade color is given by default it will fade to 80% gray. - */ - private static final int DEFAULT_FADE_COLOR = 0x99000000; - - /** - * Default Minimum velocity that will be detected as a fling - */ - private static final int DEFAULT_MIN_FLING_VELOCITY = 400; // dips per second - /** - * Default is set to false because that is how it was written - */ - private static final boolean DEFAULT_OVERLAY_FLAG = false; - /** - * Default is set to true for clip panel for performance reasons - */ - private static final boolean DEFAULT_CLIP_PANEL_FLAG = true; - /** - * Default attributes for layout - */ - private static final int[] DEFAULT_ATTRS = new int[]{ - android.R.attr.gravity - }; - /** - * Default parallax length of the main view - */ - private static final int DEFAULT_PARALLAX_OFFSET = 0; - /** - * Default initial state for the component - */ - private static final PanelState DEFAULT_SLIDE_STATE = PanelState.COLLAPSED; - /** - * The paint used to dim the main layout when sliding - */ - private final Paint mCoveredFadePaint = new Paint(); - /** - * Drawable used to draw the shadow between panes. - */ - private final Drawable mShadowDrawable; - private final List<PanelSlideListener> mPanelSlideListeners = new CopyOnWriteArrayList<>(); - private final ViewDragHelper mDragHelper; - private final Rect mTmpRect = new Rect(); - /** - * Minimum velocity that will be detected as a fling - */ - private int mMinFlingVelocity = DEFAULT_MIN_FLING_VELOCITY; - /** - * The fade color used for the panel covered by the slider. 0 = no fading. - */ - private int mCoveredFadeColor = DEFAULT_FADE_COLOR; - /** - * The size of the overhang in pixels. - */ - private int mPanelHeight = -1; - /** - * The size of the shadow in pixels. - */ - private int mShadowHeight = -1; - /** - * Parallax offset - */ - private int mParallaxOffset = -1; - /** - * True if the collapsed panel should be dragged up. - */ - private boolean mIsSlidingUp; - /** - * Panel overlays the windows instead of putting it underneath it. - */ - private boolean mOverlayContent = DEFAULT_OVERLAY_FLAG; - /** - * The main view is clipped to the main top border - */ - private boolean mClipPanel = DEFAULT_CLIP_PANEL_FLAG; - /** - * If provided, the panel can be dragged by only this view. Otherwise, the entire panel can be - * used for dragging. - */ - private View mDragView; - /** - * If provided, the panel can be dragged by only this view. Otherwise, the entire panel can be - * used for dragging. - */ - private int mDragViewResId = -1; - /** - * If provided, the panel can not be dragged by this view. Otherwise, the entire panel can be - * used for dragging. - */ - private View mAntiDragView; - /** - * If provided, the panel can not be dragged by this view. Otherwise, the entire panel can be - * used for dragging. - */ - private int mAntiDragViewResId = -1; - /** - * If provided, the panel will transfer the scroll from this view to itself when needed. - */ - private View mScrollableView; - private int mScrollableViewResId; - private ScrollableViewHelper mScrollableViewHelper = new ScrollableViewHelper(); - /** - * The child view that can slide, if any. - */ - private View mSlideableView; - /** - * The main view - */ - private View mMainView; - private PanelState mSlideState = DEFAULT_SLIDE_STATE; - /** - * If the current slide state is DRAGGING, this will store the last non dragging state - */ - private PanelState mLastNotDraggingSlideState = DEFAULT_SLIDE_STATE; - /** - * How far the panel is offset from its expanded position. - * range [0, 1] where 0 = collapsed, 1 = expanded. - */ - private float mSlideOffset; - /** - * How far in pixels the slideable panel may move. - */ - private int mSlideRange; - /** - * An anchor point where the panel can stop during sliding - */ - private float mAnchorPoint = 1.f; - /** - * A panel view is locked into internal scrolling or another condition that - * is preventing a drag. - */ - private boolean mIsUnableToDrag; - /** - * Flag indicating that sliding feature is enabled\disabled - */ - private boolean mIsTouchEnabled; - private float mPrevMotionX; - private float mPrevMotionY; - private float mInitialMotionX; - private float mInitialMotionY; - private boolean mIsScrollableViewHandlingTouch = false; - private View.OnClickListener mFadeOnClickListener; - /** - * Stores whether or not the pane was expanded the last time it was slideable. - * If expand/collapse operations are invoked this state is modified. Used by - * instance state save/restore. - */ - private boolean mFirstLayout = true; - - public SlidingUpPanelLayout(Context context) { - this(context, null); - } - - public SlidingUpPanelLayout(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public SlidingUpPanelLayout(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - - if (isInEditMode()) { - mShadowDrawable = null; - mDragHelper = null; - return; - } - - Interpolator scrollerInterpolator = null; - if (attrs != null) { - TypedArray defAttrs = context.obtainStyledAttributes(attrs, DEFAULT_ATTRS); - - if (defAttrs != null) { - int gravity = defAttrs.getInt(0, Gravity.NO_GRAVITY); - setGravity(gravity); - defAttrs.recycle(); - } - - - TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.SlidingUpPanelLayout); - - if (ta != null) { - mPanelHeight = ta.getDimensionPixelSize(R.styleable.SlidingUpPanelLayout_umanoPanelHeight, -1); - mShadowHeight = ta.getDimensionPixelSize(R.styleable.SlidingUpPanelLayout_umanoShadowHeight, -1); - mParallaxOffset = ta.getDimensionPixelSize(R.styleable.SlidingUpPanelLayout_umanoParallaxOffset, -1); - - mMinFlingVelocity = ta.getInt(R.styleable.SlidingUpPanelLayout_umanoFlingVelocity, DEFAULT_MIN_FLING_VELOCITY); - mCoveredFadeColor = ta.getColor(R.styleable.SlidingUpPanelLayout_umanoFadeColor, DEFAULT_FADE_COLOR); - - mDragViewResId = ta.getResourceId(R.styleable.SlidingUpPanelLayout_umanoDragView, -1); - mAntiDragViewResId = ta.getResourceId(R.styleable.SlidingUpPanelLayout_umanoAntiDragView, -1); - mScrollableViewResId = ta.getResourceId(R.styleable.SlidingUpPanelLayout_umanoScrollableView, -1); - - mOverlayContent = ta.getBoolean(R.styleable.SlidingUpPanelLayout_umanoOverlay, DEFAULT_OVERLAY_FLAG); - mClipPanel = ta.getBoolean(R.styleable.SlidingUpPanelLayout_umanoClipPanel, DEFAULT_CLIP_PANEL_FLAG); - - mAnchorPoint = ta.getFloat(R.styleable.SlidingUpPanelLayout_umanoAnchorPoint, DEFAULT_ANCHOR_POINT); - - mSlideState = PanelState.values()[ta.getInt(R.styleable.SlidingUpPanelLayout_umanoInitialState, DEFAULT_SLIDE_STATE.ordinal())]; - - int interpolatorResId = ta.getResourceId(R.styleable.SlidingUpPanelLayout_umanoScrollInterpolator, -1); - if (interpolatorResId != -1) { - scrollerInterpolator = AnimationUtils.loadInterpolator(context, interpolatorResId); - } - ta.recycle(); - } - } - - final float density = context.getResources().getDisplayMetrics().density; - if (mPanelHeight == -1) { - mPanelHeight = (int) (DEFAULT_PANEL_HEIGHT * density + 0.5f); - } - if (mShadowHeight == -1) { - mShadowHeight = (int) (DEFAULT_SHADOW_HEIGHT * density + 0.5f); - } - if (mParallaxOffset == -1) { - mParallaxOffset = (int) (DEFAULT_PARALLAX_OFFSET * density); - } - // If the shadow height is zero, don't show the shadow - if (mShadowHeight > 0) { - if (mIsSlidingUp) { - mShadowDrawable = getResources().getDrawable(R.drawable.above_shadow); - } else { - mShadowDrawable = getResources().getDrawable(R.drawable.below_shadow); - } - } else { - mShadowDrawable = null; - } - - setWillNotDraw(false); - - mDragHelper = ViewDragHelper.create(this, 0.5f, scrollerInterpolator, new DragHelperCallback()); - mDragHelper.setMinVelocity(mMinFlingVelocity * density); - - mIsTouchEnabled = true; - } - - private static boolean hasOpaqueBackground(View v) { - final Drawable bg = v.getBackground(); - return bg != null && bg.getOpacity() == PixelFormat.OPAQUE; - } - - /** - * Set the Drag View after the view is inflated - */ - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - if (mDragViewResId != -1) { - setDragView(findViewById(mDragViewResId)); - } - if (mAntiDragViewResId != -1) { - setAntiDragView(findViewById(mAntiDragViewResId)); - } - if (mScrollableViewResId != -1) { - setScrollableView(findViewById(mScrollableViewResId)); - } - } - - public void setGravity(int gravity) { - if (gravity != Gravity.TOP && gravity != Gravity.BOTTOM) { - throw new IllegalArgumentException("gravity must be set to either top or bottom"); - } - mIsSlidingUp = gravity == Gravity.BOTTOM; - if (!mFirstLayout) { - requestLayout(); - } - } - - /** - * @return The ARGB-packed color value used to fade the fixed pane - */ - public int getCoveredFadeColor() { - return mCoveredFadeColor; - } - - /** - * Set the color used to fade the pane covered by the sliding pane out when the pane - * will become fully covered in the expanded state. - * - * @param color An ARGB-packed color value - */ - public void setCoveredFadeColor(int color) { - mCoveredFadeColor = color; - requestLayout(); - } - - public boolean isTouchEnabled() { - return mIsTouchEnabled && mSlideableView != null && mSlideState != PanelState.HIDDEN; - } - - /** - * Set sliding enabled flag - * - * @param enabled flag value - */ - public void setTouchEnabled(boolean enabled) { - mIsTouchEnabled = enabled; - } - - protected void smoothToBottom() { - smoothSlideTo(0, 0); - } - - /** - * @return The current shadow height - */ - public int getShadowHeight() { - return mShadowHeight; - } - - /** - * Set the shadow height - * - * @param val A height in pixels - */ - public void setShadowHeight(int val) { - mShadowHeight = val; - if (!mFirstLayout) { - invalidate(); - } - } - - /** - * @return The current collapsed panel height - */ - public int getPanelHeight() { - return mPanelHeight; - } - - /** - * Set the collapsed panel height in pixels - * - * @param val A height in pixels - */ - public void setPanelHeight(int val) { - if (getPanelHeight() == val) { - return; - } - - mPanelHeight = val; - if (!mFirstLayout) { - requestLayout(); - } - - if (getPanelState() == PanelState.COLLAPSED) { - smoothToBottom(); - invalidate(); - } - } - - /** - * @return The current parallax offset - */ - public int getCurrentParallaxOffset() { - // Clamp slide offset at zero for parallax computation; - int offset = (int) (mParallaxOffset * Math.max(mSlideOffset, 0)); - return mIsSlidingUp ? -offset : offset; - } - - /** - * Set parallax offset for the panel - * - * @param val A height in pixels - */ - public void setParallaxOffset(int val) { - mParallaxOffset = val; - if (!mFirstLayout) { - requestLayout(); - } - } - - /** - * @return The current minimin fling velocity - */ - public int getMinFlingVelocity() { - return mMinFlingVelocity; - } - - /** - * Sets the minimum fling velocity for the panel - * - * @param val the new value - */ - public void setMinFlingVelocity(int val) { - mMinFlingVelocity = val; - } - - /** - * Adds a panel slide listener - */ - public void addPanelSlideListener(PanelSlideListener listener) { - synchronized (mPanelSlideListeners) { - mPanelSlideListeners.add(listener); - } - } - - /** - * Removes a panel slide listener - */ - public void removePanelSlideListener(PanelSlideListener listener) { - synchronized (mPanelSlideListeners) { - mPanelSlideListeners.remove(listener); - } - } - - /** - * Provides an on click for the portion of the main view that is dimmed. The listener is not - * triggered if the panel is in a collapsed or a hidden position. If the on click listener is - * not provided, the clicks on the dimmed area are passed through to the main layout. - */ - public void setFadeOnClickListener(View.OnClickListener listener) { - mFadeOnClickListener = listener; - } - - /** - * Set the draggable view portion. Use to null, to allow the whole panel to be draggable - * - * @param dragView A view that will be used to drag the panel. - */ - public void setDragView(View dragView) { - if (mDragView != null) { - mDragView.setOnClickListener(null); - } - mDragView = dragView; - if (mDragView != null) { - mDragView.setClickable(true); - mDragView.setFocusable(false); - mDragView.setFocusableInTouchMode(false); - mDragView.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - if (!isEnabled() || !isTouchEnabled()) return; - if (mSlideState != PanelState.EXPANDED && mSlideState != PanelState.ANCHORED) { - if (mAnchorPoint < 1.0f) { - setPanelState(PanelState.ANCHORED); - } else { - setPanelState(PanelState.EXPANDED); - } - } else { - setPanelState(PanelState.COLLAPSED); - } - } - }); - } - } - - /** - * Set the draggable view portion. Use to null, to allow the whole panel to be draggable - * - * @param dragViewResId The resource ID of the new drag view - */ - public void setDragView(int dragViewResId) { - mDragViewResId = dragViewResId; - setDragView(findViewById(dragViewResId)); - } - - /** - * Set a portion of the view that shall not be draggable. Use null to make everything draggable - * - * @param antiDragViewResId The resource ID of the anti drag view - */ - public void setAntiDragView(int antiDragViewResId) { - mAntiDragViewResId = antiDragViewResId; - setAntiDragView(findViewById(antiDragViewResId)); - } - - /** - * Set a portion of the view that shall not be draggable. Use null to make everything draggable - * - * @param antiDragView A view that will be used to drag the panel. - */ - public void setAntiDragView(View antiDragView) { - this.mAntiDragView = antiDragView; - } - - /** - * Set the scrollable child of the sliding layout. If set, scrolling will be transfered between - * the panel and the view when necessary - * - * @param scrollableView The scrollable view - */ - public void setScrollableView(View scrollableView) { - mScrollableView = scrollableView; - } - - /** - * Sets the current scrollable view helper. See ScrollableViewHelper description for details. - */ - public void setScrollableViewHelper(ScrollableViewHelper helper) { - mScrollableViewHelper = helper; - } - - /** - * Gets the currently set anchor point - * - * @return the currently set anchor point - */ - public float getAnchorPoint() { - return mAnchorPoint; - } - - /** - * Set an anchor point where the panel can stop during sliding - * - * @param anchorPoint A value between 0 and 1, determining the position of the anchor point - * starting from the top of the layout. - */ - public void setAnchorPoint(float anchorPoint) { - if (anchorPoint > 0 && anchorPoint <= 1) { - mAnchorPoint = anchorPoint; - mFirstLayout = true; - requestLayout(); - } - } - - /** - * Check if the panel is set as an overlay. - */ - public boolean isOverlayed() { - return mOverlayContent; - } - - /** - * Sets whether or not the panel overlays the content - */ - public void setOverlayed(boolean overlayed) { - mOverlayContent = overlayed; - } - - /** - * Check whether or not the main content is clipped to the top of the panel - */ - public boolean isClipPanel() { - return mClipPanel; - } - - /** - * Sets whether or not the main content is clipped to the top of the panel - */ - public void setClipPanel(boolean clip) { - mClipPanel = clip; - } - - void dispatchOnPanelSlide(View panel) { - synchronized (mPanelSlideListeners) { - for (PanelSlideListener l : mPanelSlideListeners) { - l.onPanelSlide(panel, mSlideOffset); - } - } - } - - void dispatchOnPanelStateChanged(View panel, PanelState previousState, PanelState newState) { - synchronized (mPanelSlideListeners) { - for (PanelSlideListener l : mPanelSlideListeners) { - l.onPanelStateChanged(panel, previousState, newState); - } - } - sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); - } - - void updateObscuredViewVisibility() { - if (getChildCount() == 0) { - return; - } - final int leftBound = getPaddingLeft(); - final int rightBound = getWidth() - getPaddingRight(); - final int topBound = getPaddingTop(); - final int bottomBound = getHeight() - getPaddingBottom(); - final int left; - final int right; - final int top; - final int bottom; - if (mSlideableView != null && hasOpaqueBackground(mSlideableView)) { - left = mSlideableView.getLeft(); - right = mSlideableView.getRight(); - top = mSlideableView.getTop(); - bottom = mSlideableView.getBottom(); - } else { - left = right = top = bottom = 0; - } - View child = getChildAt(0); - final int clampedChildLeft = Math.max(leftBound, child.getLeft()); - final int clampedChildTop = Math.max(topBound, child.getTop()); - final int clampedChildRight = Math.min(rightBound, child.getRight()); - final int clampedChildBottom = Math.min(bottomBound, child.getBottom()); - final int vis; - if (clampedChildLeft >= left && clampedChildTop >= top && - clampedChildRight <= right && clampedChildBottom <= bottom) { - vis = INVISIBLE; - } else { - vis = VISIBLE; - } - child.setVisibility(vis); - } - - void setAllChildrenVisible() { - for (int i = 0, childCount = getChildCount(); i < childCount; i++) { - final View child = getChildAt(i); - if (child.getVisibility() == INVISIBLE) { - child.setVisibility(VISIBLE); - } - } - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - mFirstLayout = true; - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - mFirstLayout = true; - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - final int widthMode = MeasureSpec.getMode(widthMeasureSpec); - final int widthSize = MeasureSpec.getSize(widthMeasureSpec); - final int heightMode = MeasureSpec.getMode(heightMeasureSpec); - final int heightSize = MeasureSpec.getSize(heightMeasureSpec); - - if (widthMode != MeasureSpec.EXACTLY && widthMode != MeasureSpec.AT_MOST) { - throw new IllegalStateException("Width must have an exact value or MATCH_PARENT"); - } else if (heightMode != MeasureSpec.EXACTLY && heightMode != MeasureSpec.AT_MOST) { - throw new IllegalStateException("Height must have an exact value or MATCH_PARENT"); - } - - final int childCount = getChildCount(); - - if (childCount != 2) { - throw new IllegalStateException("Sliding up panel layout must have exactly 2 children!"); - } - - mMainView = getChildAt(0); - mSlideableView = getChildAt(1); - if (mDragView == null) { - setDragView(mSlideableView); - } - - // If the sliding panel is not visible, then put the whole view in the hidden state - if (mSlideableView.getVisibility() != VISIBLE) { - mSlideState = PanelState.HIDDEN; - } - - int layoutHeight = heightSize - getPaddingTop() - getPaddingBottom(); - int layoutWidth = widthSize - getPaddingLeft() - getPaddingRight(); - - // First pass. Measure based on child LayoutParams width/height. - for (int i = 0; i < childCount; i++) { - final View child = getChildAt(i); - final LayoutParams lp = (LayoutParams) child.getLayoutParams(); - - // We always measure the sliding panel in order to know it's height (needed for show panel) - if (child.getVisibility() == GONE && i == 0) { - continue; - } - - int height = layoutHeight; - int width = layoutWidth; - if (child == mMainView) { - if (!mOverlayContent && mSlideState != PanelState.HIDDEN) { - height -= mPanelHeight; - } - - width -= lp.leftMargin + lp.rightMargin; - } else if (child == mSlideableView) { - // The slideable view should be aware of its top margin. - // See https://github.com/umano/AndroidSlidingUpPanel/issues/412. - height -= lp.topMargin; - } - - int childWidthSpec; - switch (lp.width) { - case LayoutParams.WRAP_CONTENT: - childWidthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST); - break; - case LayoutParams.MATCH_PARENT: - childWidthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY); - break; - default: - childWidthSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY); - break; - } - - int childHeightSpec; - if (lp.height == LayoutParams.WRAP_CONTENT) { - childHeightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST); - } else { - // Modify the height based on the weight. - if (lp.weight > 0 && lp.weight < 1) { - height = (int) (height * lp.weight); - } else if (lp.height != LayoutParams.MATCH_PARENT) { - height = lp.height; - } - childHeightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY); - } - - child.measure(childWidthSpec, childHeightSpec); - - if (child == mSlideableView) { - mSlideRange = mSlideableView.getMeasuredHeight() - mPanelHeight; - } - } - - setMeasuredDimension(widthSize, heightSize); - } - - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - final int paddingLeft = getPaddingLeft(); - final int paddingTop = getPaddingTop(); - - final int childCount = getChildCount(); - - if (mFirstLayout) { - switch (mSlideState) { - case EXPANDED: - mSlideOffset = 1.0f; - break; - case ANCHORED: - mSlideOffset = mAnchorPoint; - break; - case HIDDEN: - int newTop = computePanelTopPosition(0.0f) + (mIsSlidingUp ? +mPanelHeight : -mPanelHeight); - mSlideOffset = computeSlideOffset(newTop); - break; - default: - mSlideOffset = 0.f; - break; - } - } - - for (int i = 0; i < childCount; i++) { - final View child = getChildAt(i); - final LayoutParams lp = (LayoutParams) child.getLayoutParams(); - - // Always layout the sliding view on the first layout - if (child.getVisibility() == GONE && (i == 0 || mFirstLayout)) { - continue; - } - - final int childHeight = child.getMeasuredHeight(); - int childTop = paddingTop; - - if (child == mSlideableView) { - childTop = computePanelTopPosition(mSlideOffset); - } - - if (!mIsSlidingUp) { - if (child == mMainView && !mOverlayContent) { - childTop = computePanelTopPosition(mSlideOffset) + mSlideableView.getMeasuredHeight(); - } - } - final int childBottom = childTop + childHeight; - final int childLeft = paddingLeft + lp.leftMargin; - final int childRight = childLeft + child.getMeasuredWidth(); - - child.layout(childLeft, childTop, childRight, childBottom); - } - - if (mFirstLayout) { - updateObscuredViewVisibility(); - } - applyParallaxForCurrentSlideOffset(); - - mFirstLayout = false; - } - - @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - super.onSizeChanged(w, h, oldw, oldh); - // Recalculate sliding panes and their details - if (h != oldh) { - mFirstLayout = true; - } - } - - @Override - public boolean onInterceptTouchEvent(MotionEvent ev) { - // If the scrollable view is handling touch, never intercept - if (mIsScrollableViewHandlingTouch || !isTouchEnabled()) { - mDragHelper.abort(); - return false; - } - - final int action = MotionEventCompat.getActionMasked(ev); - final float x = ev.getX(); - final float y = ev.getY(); - final float adx = Math.abs(x - mInitialMotionX); - final float ady = Math.abs(y - mInitialMotionY); - final int dragSlop = mDragHelper.getTouchSlop(); - - switch (action) { - case MotionEvent.ACTION_DOWN: { - mIsUnableToDrag = false; - mInitialMotionX = x; - mInitialMotionY = y; - if (!checkTouchingDragView(mDragView, (int) x, (int) y)) { - mDragHelper.cancel(); - mIsUnableToDrag = true; - return false; - } - - break; - } - - case MotionEvent.ACTION_MOVE: { - if (ady > dragSlop && adx > ady) { - mDragHelper.cancel(); - mIsUnableToDrag = true; - return false; - } - break; - } - - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_UP: - // If the dragView is still dragging when we get here, we need to call processTouchEvent - // so that the view is settled - // Added to make scrollable views work (tokudu) - if (mDragHelper.isDragging()) { - mDragHelper.processTouchEvent(ev); - return true; - } - // Check if this was a click on the faded part of the screen, and fire off the listener if there is one. - if (ady <= dragSlop - && adx <= dragSlop - && mSlideOffset > 0 && !checkTouchingDragView(mSlideableView, (int) mInitialMotionX, (int) mInitialMotionY) && mFadeOnClickListener != null) { - playSoundEffect(android.view.SoundEffectConstants.CLICK); - mFadeOnClickListener.onClick(this); - return true; - } - break; - } - return mDragHelper.shouldInterceptTouchEvent(ev); - } - - @SuppressLint("ClickableViewAccessibility") - @Override - public boolean onTouchEvent(MotionEvent ev) { - if (!isEnabled() || !isTouchEnabled()) { - return super.onTouchEvent(ev); - } - try { - mDragHelper.processTouchEvent(ev); - return true; - } catch (Exception ex) { - // Ignore the pointer out of range exception - return false; - } - } - - @Override - public boolean dispatchTouchEvent(MotionEvent ev) { - final int action = MotionEventCompat.getActionMasked(ev); - - if (!isEnabled() || !isTouchEnabled() || (mIsUnableToDrag && action != MotionEvent.ACTION_DOWN)) { - mDragHelper.abort(); - return super.dispatchTouchEvent(ev); - } - - final float x = ev.getX(); - final float y = ev.getY(); - - switch (action) { - case MotionEvent.ACTION_DOWN: - mIsScrollableViewHandlingTouch = false; - mPrevMotionX = x; - mPrevMotionY = y; - break; - case MotionEvent.ACTION_MOVE: - float dx = x - mPrevMotionX; - float dy = y - mPrevMotionY; - mPrevMotionX = x; - mPrevMotionY = y; - - if (Math.abs(dx) > Math.abs(dy)) { - // Scrolling horizontally, so ignore - return super.dispatchTouchEvent(ev); - } - - // If the scroll view isn't under the touch, pass the - // event along to the dragView. - if (!checkTouchingDragView(mScrollableView, (int) mInitialMotionX, (int) mInitialMotionY)) { - return super.dispatchTouchEvent(ev); - } - - // Which direction (up or down) is the drag moving? - if (dy * (mIsSlidingUp ? 1 : -1) > 0) { // Collapsing - // Is the child less than fully scrolled? - // Then let the child handle it. - if (mScrollableViewHelper.getScrollableViewScrollPosition(mScrollableView, mIsSlidingUp) > 0) { - mIsScrollableViewHandlingTouch = true; - return super.dispatchTouchEvent(ev); - } - - // Was the child handling the touch previously? - // Then we need to rejigger things so that the - // drag panel gets a proper down event. - if (mIsScrollableViewHandlingTouch) { - // Send an 'UP' event to the child. - MotionEvent up = MotionEvent.obtain(ev); - up.setAction(MotionEvent.ACTION_CANCEL); - super.dispatchTouchEvent(up); - up.recycle(); - - // Send a 'DOWN' event to the panel. (We'll cheat - // and hijack this one) - ev.setAction(MotionEvent.ACTION_DOWN); - } - - mIsScrollableViewHandlingTouch = false; - return this.onTouchEvent(ev); - } else if (dy * (mIsSlidingUp ? 1 : -1) < 0) { // Expanding - // Is the panel less than fully expanded? - // Then we'll handle the drag here. - if (mSlideOffset < 1.0f) { - mIsScrollableViewHandlingTouch = false; - return this.onTouchEvent(ev); - } - - // Was the panel handling the touch previously? - // Then we need to rejigger things so that the - // child gets a proper down event. - if (!mIsScrollableViewHandlingTouch && mDragHelper.isDragging()) { - mDragHelper.cancel(); - ev.setAction(MotionEvent.ACTION_DOWN); - } - - mIsScrollableViewHandlingTouch = true; - return super.dispatchTouchEvent(ev); - } - break; - case MotionEvent.ACTION_UP: - // If the scrollable view was handling the touch and we receive an up - // we want to clear any previous dragging state so we don't intercept a touch stream accidentally - if (mIsScrollableViewHandlingTouch) { - mDragHelper.setDragState(ViewDragHelper.STATE_IDLE); - } - break; - } - - // In all other cases, just let the default behavior take over. - return super.dispatchTouchEvent(ev); - } - - private boolean checkTouchingDragView(View view, int x, int y) { - return isViewUnder(view, x, y) && !isViewUnder(mAntiDragView, x, y); - } - - private boolean isViewUnder(View view, int x, int y) { - if (view == null) return false; - int[] viewLocation = new int[2]; - view.getLocationOnScreen(viewLocation); - int[] parentLocation = new int[2]; - this.getLocationOnScreen(parentLocation); - int screenX = parentLocation[0] + x; - int screenY = parentLocation[1] + y; - return screenX >= viewLocation[0] && screenX < viewLocation[0] + view.getWidth() && - screenY >= viewLocation[1] && screenY < viewLocation[1] + view.getHeight(); - } - - /* - * Computes the top position of the panel based on the slide offset. - */ - private int computePanelTopPosition(float slideOffset) { - int slidingViewHeight = mSlideableView != null ? mSlideableView.getMeasuredHeight() : 0; - int slidePixelOffset = (int) (slideOffset * mSlideRange); - // Compute the top of the panel if its collapsed - return mIsSlidingUp - ? getMeasuredHeight() - getPaddingBottom() - mPanelHeight - slidePixelOffset - : getPaddingTop() - slidingViewHeight + mPanelHeight + slidePixelOffset; - } - - /* - * Computes the slide offset based on the top position of the panel - */ - private float computeSlideOffset(int topPosition) { - // Compute the panel top position if the panel is collapsed (offset 0) - final int topBoundCollapsed = computePanelTopPosition(0); - - // Determine the new slide offset based on the collapsed top position and the new required - // top position - return (mIsSlidingUp - ? (float) (topBoundCollapsed - topPosition) / mSlideRange - : (float) (topPosition - topBoundCollapsed) / mSlideRange); - } - - /** - * Returns the current state of the panel as an enum. - * - * @return the current panel state - */ - public PanelState getPanelState() { - return mSlideState; - } - - /** - * Change panel state to the given state with - * - * @param state - new panel state - */ - public void setPanelState(PanelState state) { - - // Abort any running animation, to allow state change - if (mDragHelper.getViewDragState() == ViewDragHelper.STATE_SETTLING) { - Log.d(TAG, "View is settling. Aborting animation."); - mDragHelper.abort(); - } - - if (state == null || state == PanelState.DRAGGING) { - throw new IllegalArgumentException("Panel state cannot be null or DRAGGING."); - } - if (!isEnabled() - || (!mFirstLayout && mSlideableView == null) - || state == mSlideState - || mSlideState == PanelState.DRAGGING) return; - - if (mFirstLayout) { - setPanelStateInternal(state); - } else { - if (mSlideState == PanelState.HIDDEN) { - mSlideableView.setVisibility(View.VISIBLE); - requestLayout(); - } - switch (state) { - case ANCHORED: - smoothSlideTo(mAnchorPoint, 0); - break; - case COLLAPSED: - smoothSlideTo(0, 0); - break; - case EXPANDED: - smoothSlideTo(1.0f, 0); - break; - case HIDDEN: - int newTop = computePanelTopPosition(0.0f) + (mIsSlidingUp ? +mPanelHeight : -mPanelHeight); - smoothSlideTo(computeSlideOffset(newTop), 0); - break; - } - } - } - - private void setPanelStateInternal(PanelState state) { - if (mSlideState == state) return; - PanelState oldState = mSlideState; - mSlideState = state; - dispatchOnPanelStateChanged(this, oldState, state); - } - - /** - * Update the parallax based on the current slide offset. - */ - @SuppressLint("NewApi") - private void applyParallaxForCurrentSlideOffset() { - if (mParallaxOffset > 0) { - int mainViewOffset = getCurrentParallaxOffset(); - ViewCompat.setTranslationY(mMainView, mainViewOffset); - } - } - - private void onPanelDragged(int newTop) { - if (mSlideState != PanelState.DRAGGING) { - mLastNotDraggingSlideState = mSlideState; - } - setPanelStateInternal(PanelState.DRAGGING); - // Recompute the slide offset based on the new top position - mSlideOffset = computeSlideOffset(newTop); - applyParallaxForCurrentSlideOffset(); - // Dispatch the slide event - dispatchOnPanelSlide(mSlideableView); - // If the slide offset is negative, and overlay is not on, we need to increase the - // height of the main content - LayoutParams lp = (LayoutParams) mMainView.getLayoutParams(); - int defaultHeight = getHeight() - getPaddingBottom() - getPaddingTop() - mPanelHeight; - - if (mSlideOffset <= 0 && !mOverlayContent) { - // expand the main view - lp.height = mIsSlidingUp ? (newTop - getPaddingBottom()) : (getHeight() - getPaddingBottom() - mSlideableView.getMeasuredHeight() - newTop); - if (lp.height == defaultHeight) { - lp.height = LayoutParams.MATCH_PARENT; - } - mMainView.requestLayout(); - } else if (lp.height != LayoutParams.MATCH_PARENT && !mOverlayContent) { - lp.height = LayoutParams.MATCH_PARENT; - mMainView.requestLayout(); - } - } - - @Override - protected boolean drawChild(Canvas canvas, View child, long drawingTime) { - boolean result; - final int save = canvas.save(); - - if (mSlideableView != null && mSlideableView != child) { // if main view - // Clip against the slider; no sense drawing what will immediately be covered, - // Unless the panel is set to overlay content - canvas.getClipBounds(mTmpRect); - if (!mOverlayContent) { - if (mIsSlidingUp) { - mTmpRect.bottom = Math.min(mTmpRect.bottom, mSlideableView.getTop()); - } else { - mTmpRect.top = Math.max(mTmpRect.top, mSlideableView.getBottom()); - } - } - if (mClipPanel) { - canvas.clipRect(mTmpRect); - } - - result = super.drawChild(canvas, child, drawingTime); - - if (mCoveredFadeColor != 0 && mSlideOffset > 0) { - final int baseAlpha = (mCoveredFadeColor & 0xff000000) >>> 24; - final int imag = (int) (baseAlpha * mSlideOffset); - final int color = imag << 24 | (mCoveredFadeColor & 0xffffff); - mCoveredFadePaint.setColor(color); - canvas.drawRect(mTmpRect, mCoveredFadePaint); - } - } else { - result = super.drawChild(canvas, child, drawingTime); - } - - canvas.restoreToCount(save); - - return result; - } - - /** - * Smoothly animate mDraggingPane to the target X position within its range. - * - * @param slideOffset position to animate to - * @param velocity initial velocity in case of fling, or 0. - */ - boolean smoothSlideTo(float slideOffset, int velocity) { - if (!isEnabled() || mSlideableView == null) { - // Nothing to do. - return false; - } - - int panelTop = computePanelTopPosition(slideOffset); - - if (mDragHelper.smoothSlideViewTo(mSlideableView, mSlideableView.getLeft(), panelTop)) { - setAllChildrenVisible(); - ViewCompat.postInvalidateOnAnimation(this); - return true; - } - return false; - } - - @Override - public void computeScroll() { - if (mDragHelper != null && mDragHelper.continueSettling(true)) { - if (!isEnabled()) { - mDragHelper.abort(); - return; - } - - ViewCompat.postInvalidateOnAnimation(this); - } - } - - @Override - public void draw(Canvas c) { - super.draw(c); - - // draw the shadow - if (mShadowDrawable != null && mSlideableView != null) { - final int right = mSlideableView.getRight(); - final int top; - final int bottom; - if (mIsSlidingUp) { - top = mSlideableView.getTop() - mShadowHeight; - bottom = mSlideableView.getTop(); - } else { - top = mSlideableView.getBottom(); - bottom = mSlideableView.getBottom() + mShadowHeight; - } - final int left = mSlideableView.getLeft(); - mShadowDrawable.setBounds(left, top, right, bottom); - mShadowDrawable.draw(c); - } - } - - /** - * Tests scrollability within child views of v given a delta of dx. - * - * @param v View to test for horizontal scrollability - * @param checkV Whether the view v passed should itself be checked for scrollability (true), - * or just its children (false). - * @param dx Delta scrolled in pixels - * @param x X coordinate of the active touch point - * @param y Y coordinate of the active touch point - * @return true if child views of v can be scrolled by delta of dx. - */ - protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) { - if (v instanceof ViewGroup) { - final ViewGroup group = (ViewGroup) v; - final int scrollX = v.getScrollX(); - final int scrollY = v.getScrollY(); - final int count = group.getChildCount(); - // Count backwards - let topmost views consume scroll distance first. - for (int i = count - 1; i >= 0; i--) { - final View child = group.getChildAt(i); - if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() && - y + scrollY >= child.getTop() && y + scrollY < child.getBottom() && - canScroll(child, true, dx, x + scrollX - child.getLeft(), - y + scrollY - child.getTop())) { - return true; - } - } - } - return checkV && ViewCompat.canScrollHorizontally(v, -dx); - } - - @Override - protected ViewGroup.LayoutParams generateDefaultLayoutParams() { - return new LayoutParams(); - } - - @Override - protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { - return p instanceof MarginLayoutParams - ? new LayoutParams((MarginLayoutParams) p) - : new LayoutParams(p); - } - - @Override - protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { - return p instanceof LayoutParams && super.checkLayoutParams(p); - } - - @Override - public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { - return new LayoutParams(getContext(), attrs); - } - - @Override - public Parcelable onSaveInstanceState() { - Bundle bundle = new Bundle(); - bundle.putParcelable("superState", super.onSaveInstanceState()); - bundle.putSerializable(SLIDING_STATE, mSlideState != PanelState.DRAGGING ? mSlideState : mLastNotDraggingSlideState); - return bundle; - } - - @Override - public void onRestoreInstanceState(Parcelable state) { - if (state instanceof Bundle) { - Bundle bundle = (Bundle) state; - mSlideState = (PanelState) bundle.getSerializable(SLIDING_STATE); - mSlideState = mSlideState == null ? DEFAULT_SLIDE_STATE : mSlideState; - state = bundle.getParcelable("superState"); - } - super.onRestoreInstanceState(state); - } - - /** - * Current state of the slideable view. - */ - public enum PanelState { - EXPANDED, - COLLAPSED, - ANCHORED, - HIDDEN, - DRAGGING - } - - /** - * Listener for monitoring events about sliding panes. - */ - public interface PanelSlideListener { - /** - * Called when a sliding pane's position changes. - * - * @param panel The child view that was moved - * @param slideOffset The new offset of this sliding pane within its range, from 0-1 - */ - void onPanelSlide(View panel, float slideOffset); - - /** - * Called when a sliding panel state changes - * - * @param panel The child view that was slid to an collapsed position - */ - void onPanelStateChanged(View panel, PanelState previousState, PanelState newState); - } - - /** - * No-op stubs for {@link PanelSlideListener}. If you only want to implement a subset - * of the listener methods you can extend this instead of implement the full interface. - */ - public static class SimplePanelSlideListener implements PanelSlideListener { - @Override - public void onPanelSlide(View panel, float slideOffset) { - } - - @Override - public void onPanelStateChanged(View panel, PanelState previousState, PanelState newState) { - } - } - - public static class LayoutParams extends ViewGroup.MarginLayoutParams { - private static final int[] ATTRS = new int[]{ - android.R.attr.layout_weight - }; - - public float weight = 0; - - public LayoutParams() { - super(MATCH_PARENT, MATCH_PARENT); - } - - public LayoutParams(int width, int height) { - super(width, height); - } - - public LayoutParams(int width, int height, float weight) { - super(width, height); - this.weight = weight; - } - - public LayoutParams(android.view.ViewGroup.LayoutParams source) { - super(source); - } - - public LayoutParams(MarginLayoutParams source) { - super(source); - } - - public LayoutParams(LayoutParams source) { - super(source); - } - - public LayoutParams(Context c, AttributeSet attrs) { - super(c, attrs); - - final TypedArray ta = c.obtainStyledAttributes(attrs, ATTRS); - if (ta != null) { - this.weight = ta.getFloat(0, 0); - ta.recycle(); - } - - - } - } - - private class DragHelperCallback extends ViewDragHelper.Callback { - - @Override - public boolean tryCaptureView(View child, int pointerId) { - return !mIsUnableToDrag && child == mSlideableView; - - } - - @Override - public void onViewDragStateChanged(int state) { - if (mDragHelper != null && mDragHelper.getViewDragState() == ViewDragHelper.STATE_IDLE) { - mSlideOffset = computeSlideOffset(mSlideableView.getTop()); - applyParallaxForCurrentSlideOffset(); - - if (mSlideOffset == 1) { - updateObscuredViewVisibility(); - setPanelStateInternal(PanelState.EXPANDED); - } else if (mSlideOffset == 0) { - setPanelStateInternal(PanelState.COLLAPSED); - } else if (mSlideOffset < 0) { - setPanelStateInternal(PanelState.HIDDEN); - mSlideableView.setVisibility(View.INVISIBLE); - } else { - updateObscuredViewVisibility(); - setPanelStateInternal(PanelState.ANCHORED); - } - } - } - - @Override - public void onViewCaptured(View capturedChild, int activePointerId) { - setAllChildrenVisible(); - } - - @Override - public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) { - onPanelDragged(top); - invalidate(); - } - - @Override - public void onViewReleased(View releasedChild, float xvel, float yvel) { - int target; - - // direction is always positive if we are sliding in the expanded direction - float direction = mIsSlidingUp ? -yvel : yvel; - - if (direction > 0 && mSlideOffset <= mAnchorPoint) { - // swipe up -> expand and stop at anchor point - target = computePanelTopPosition(mAnchorPoint); - } else if (direction > 0 && mSlideOffset > mAnchorPoint) { - // swipe up past anchor -> expand - target = computePanelTopPosition(1.0f); - } else if (direction < 0 && mSlideOffset >= mAnchorPoint) { - // swipe down -> collapse and stop at anchor point - target = computePanelTopPosition(mAnchorPoint); - } else if (direction < 0 && mSlideOffset < mAnchorPoint) { - // swipe down past anchor -> collapse - target = computePanelTopPosition(0.0f); - } else if (mSlideOffset >= (1.f + mAnchorPoint) / 2) { - // zero velocity, and far enough from anchor point => expand to the top - target = computePanelTopPosition(1.0f); - } else if (mSlideOffset >= mAnchorPoint / 2) { - // zero velocity, and close enough to anchor point => go to anchor - target = computePanelTopPosition(mAnchorPoint); - } else { - // settle at the bottom - target = computePanelTopPosition(0.0f); - } - - if (mDragHelper != null) { - mDragHelper.settleCapturedViewAt(releasedChild.getLeft(), target); - } - invalidate(); - } - - @Override - public int getViewVerticalDragRange(View child) { - return mSlideRange; - } - - @Override - public int clampViewPositionVertical(View child, int top, int dy) { - final int collapsedTop = computePanelTopPosition(0.f); - final int expandedTop = computePanelTopPosition(1.0f); - if (mIsSlidingUp) { - return Math.min(Math.max(top, expandedTop), collapsedTop); - } else { - return Math.min(Math.max(top, collapsedTop), expandedTop); - } - } - } -} diff --git a/slidingpanel/src/main/java/com/sothree/slidinguppanel/ViewDragHelper.java b/slidingpanel/src/main/java/com/sothree/slidinguppanel/ViewDragHelper.java deleted file mode 100644 index cea894fbe069aa1a716b20ebcfc448678ea13497..0000000000000000000000000000000000000000 --- a/slidingpanel/src/main/java/com/sothree/slidinguppanel/ViewDragHelper.java +++ /dev/null @@ -1,1481 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * 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. - */ - - -package com.sothree.slidinguppanel; - -import android.content.Context; -import android.support.v4.view.MotionEventCompat; -import android.support.v4.view.VelocityTrackerCompat; -import android.support.v4.view.ViewCompat; -import android.support.v4.widget.ScrollerCompat; -import android.view.MotionEvent; -import android.view.VelocityTracker; -import android.view.View; -import android.view.ViewConfiguration; -import android.view.ViewGroup; -import android.view.animation.Interpolator; - -import java.util.Arrays; - -/** - * ViewDragHelper is a utility class for writing custom ViewGroups. It offers a number - * of useful operations and state tracking for allowing a user to drag and reposition - * views within their parent ViewGroup. - */ -public class ViewDragHelper { - /** - * A null/invalid pointer ID. - */ - public static final int INVALID_POINTER = -1; - /** - * A view is not currently being dragged or animating as a result of a fling/snap. - */ - public static final int STATE_IDLE = 0; - /** - * A view is currently being dragged. The position is currently changing as a result - * of user input or simulated user input. - */ - public static final int STATE_DRAGGING = 1; - /** - * A view is currently settling into place as a result of a fling or - * predefined non-interactive motion. - */ - public static final int STATE_SETTLING = 2; - /** - * Edge flag indicating that the left edge should be affected. - */ - public static final int EDGE_LEFT = 1 << 0; - /** - * Edge flag indicating that the right edge should be affected. - */ - public static final int EDGE_RIGHT = 1 << 1; - /** - * Edge flag indicating that the top edge should be affected. - */ - public static final int EDGE_TOP = 1 << 2; - /** - * Edge flag indicating that the bottom edge should be affected. - */ - public static final int EDGE_BOTTOM = 1 << 3; - /** - * Edge flag set indicating all edges should be affected. - */ - public static final int EDGE_ALL = EDGE_LEFT | EDGE_TOP | EDGE_RIGHT | EDGE_BOTTOM; - /** - * Indicates that a check should occur along the horizontal axis - */ - public static final int DIRECTION_HORIZONTAL = 1 << 0; - /** - * Indicates that a check should occur along the vertical axis - */ - public static final int DIRECTION_VERTICAL = 1 << 1; - /** - * Indicates that a check should occur along all axes - */ - public static final int DIRECTION_ALL = DIRECTION_HORIZONTAL | DIRECTION_VERTICAL; - private static final String TAG = "ViewDragHelper"; - private static final int EDGE_SIZE = 20; // dp - - private static final int BASE_SETTLE_DURATION = 256; // ms - private static final int MAX_SETTLE_DURATION = 600; // ms - /** - * Interpolator defining the animation curve for mScroller - */ - private static final Interpolator sInterpolator = new Interpolator() { - public float getInterpolation(float t) { - t -= 1.0f; - return t * t * t * t * t + 1.0f; - } - }; - private final Callback mCallback; - private final ViewGroup mParentView; - // Current drag state; idle, dragging or settling - private int mDragState; - // Distance to travel before a drag may begin - private int mTouchSlop; - // Last known position/pointer tracking - private int mActivePointerId = INVALID_POINTER; - private float[] mInitialMotionX; - private float[] mInitialMotionY; - private float[] mLastMotionX; - private float[] mLastMotionY; - private int[] mInitialEdgesTouched; - private int[] mEdgeDragsInProgress; - private int[] mEdgeDragsLocked; - private int mPointersDown; - private VelocityTracker mVelocityTracker; - private float mMaxVelocity; - private float mMinVelocity; - private int mEdgeSize; - private int mTrackingEdges; - private ScrollerCompat mScroller; - private View mCapturedView; - private final Runnable mSetIdleRunnable = new Runnable() { - public void run() { - setDragState(STATE_IDLE); - } - }; - private boolean mReleaseInProgress; - - /** - * Apps should use ViewDragHelper.create() to get a new instance. - * This will allow VDH to use internal compatibility implementations for different - * platform versions. - * If the interpolator is null, the default interpolator will be used. - * - * @param context Context to initialize config-dependent params from - * @param forParent Parent view to monitor - * @param interpolator interpolator for scroller - */ - private ViewDragHelper(Context context, ViewGroup forParent, Interpolator interpolator, Callback cb) { - if (forParent == null) { - throw new IllegalArgumentException("Parent view may not be null"); - } - if (cb == null) { - throw new IllegalArgumentException("Callback may not be null"); - } - - mParentView = forParent; - mCallback = cb; - - final ViewConfiguration vc = ViewConfiguration.get(context); - final float density = context.getResources().getDisplayMetrics().density; - mEdgeSize = (int) (EDGE_SIZE * density + 0.5f); - - mTouchSlop = vc.getScaledTouchSlop(); - mMaxVelocity = vc.getScaledMaximumFlingVelocity(); - mMinVelocity = vc.getScaledMinimumFlingVelocity(); - mScroller = ScrollerCompat.create(context, interpolator != null ? interpolator : sInterpolator); - } - - /** - * Factory method to create a new ViewDragHelper. - * - * @param forParent Parent view to monitor - * @param cb Callback to provide information and receive events - * @return a new ViewDragHelper instance - */ - public static ViewDragHelper create(ViewGroup forParent, Callback cb) { - return new ViewDragHelper(forParent.getContext(), forParent, null, cb); - } - - /** - * Factory method to create a new ViewDragHelper with the specified interpolator. - * - * @param forParent Parent view to monitor - * @param interpolator interpolator for scroller - * @param cb Callback to provide information and receive events - * @return a new ViewDragHelper instance - */ - public static ViewDragHelper create(ViewGroup forParent, Interpolator interpolator, Callback cb) { - return new ViewDragHelper(forParent.getContext(), forParent, interpolator, cb); - } - - /** - * Factory method to create a new ViewDragHelper. - * - * @param forParent Parent view to monitor - * @param sensitivity Multiplier for how sensitive the helper should be about detecting - * the start of a drag. Larger values are more sensitive. 1.0f is normal. - * @param cb Callback to provide information and receive events - * @return a new ViewDragHelper instance - */ - public static ViewDragHelper create(ViewGroup forParent, float sensitivity, Callback cb) { - final ViewDragHelper helper = create(forParent, cb); - helper.mTouchSlop = (int) (helper.mTouchSlop * (1 / sensitivity)); - return helper; - } - - /** - * Factory method to create a new ViewDragHelper with the specified interpolator. - * - * @param forParent Parent view to monitor - * @param sensitivity Multiplier for how sensitive the helper should be about detecting - * the start of a drag. Larger values are more sensitive. 1.0f is normal. - * @param interpolator interpolator for scroller - * @param cb Callback to provide information and receive events - * @return a new ViewDragHelper instance - */ - public static ViewDragHelper create(ViewGroup forParent, float sensitivity, Interpolator interpolator, Callback cb) { - final ViewDragHelper helper = create(forParent, interpolator, cb); - helper.mTouchSlop = (int) (helper.mTouchSlop * (1 / sensitivity)); - return helper; - } - - /** - * Return the currently configured minimum velocity. Any flings with a magnitude less - * than this value in pixels per second. Callback methods accepting a velocity will receive - * zero as a velocity value if the real detected velocity was below this threshold. - * - * @return the minimum velocity that will be detected - */ - public float getMinVelocity() { - return mMinVelocity; - } - - /** - * Set the minimum velocity that will be detected as having a magnitude greater than zero - * in pixels per second. Callback methods accepting a velocity will be clamped appropriately. - * - * @param minVel Minimum velocity to detect - */ - public void setMinVelocity(float minVel) { - mMinVelocity = minVel; - } - - /** - * Retrieve the current drag state of this helper. This will return one of - * {@link #STATE_IDLE}, {@link #STATE_DRAGGING} or {@link #STATE_SETTLING}. - * - * @return The current drag state - */ - public int getViewDragState() { - return mDragState; - } - - /** - * Enable edge tracking for the selected edges of the parent view. - * The callback's {@link Callback#onEdgeTouched(int, int)} and - * {@link Callback#onEdgeDragStarted(int, int)} methods will only be invoked - * for edges for which edge tracking has been enabled. - * - * @param edgeFlags Combination of edge flags describing the edges to watch - * @see #EDGE_LEFT - * @see #EDGE_TOP - * @see #EDGE_RIGHT - * @see #EDGE_BOTTOM - */ - public void setEdgeTrackingEnabled(int edgeFlags) { - mTrackingEdges = edgeFlags; - } - - /** - * Return the size of an edge. This is the range in pixels along the edges of this view - * that will actively detect edge touches or drags if edge tracking is enabled. - * - * @return The size of an edge in pixels - * @see #setEdgeTrackingEnabled(int) - */ - public int getEdgeSize() { - return mEdgeSize; - } - - /** - * Capture a specific child view for dragging within the parent. The callback will be notified - * but {@link Callback#tryCaptureView(android.view.View, int)} will not be asked permission to - * capture this view. - * - * @param childView Child view to capture - * @param activePointerId ID of the pointer that is dragging the captured child view - */ - public void captureChildView(View childView, int activePointerId) { - if (childView.getParent() != mParentView) { - throw new IllegalArgumentException("captureChildView: parameter must be a descendant " + - "of the ViewDragHelper's tracked parent view (" + mParentView + ")"); - } - - mCapturedView = childView; - mActivePointerId = activePointerId; - mCallback.onViewCaptured(childView, activePointerId); - setDragState(STATE_DRAGGING); - } - - /** - * @return The currently captured view, or null if no view has been captured. - */ - public View getCapturedView() { - return mCapturedView; - } - - /** - * @return The ID of the pointer currently dragging the captured view, - * or {@link #INVALID_POINTER}. - */ - public int getActivePointerId() { - return mActivePointerId; - } - - /** - * @return The minimum distance in pixels that the user must travel to initiate a drag - */ - public int getTouchSlop() { - return mTouchSlop; - } - - /** - * The result of a call to this method is equivalent to - * {@link #processTouchEvent(android.view.MotionEvent)} receiving an ACTION_CANCEL event. - */ - public void cancel() { - mActivePointerId = INVALID_POINTER; - clearMotionHistory(); - - if (mVelocityTracker != null) { - mVelocityTracker.recycle(); - mVelocityTracker = null; - } - } - - /** - * {@link #cancel()}, but also abort all motion in progress and snap to the end of any - * animation. - */ - public void abort() { - cancel(); - if (mDragState == STATE_SETTLING) { - final int oldX = mScroller.getCurrX(); - final int oldY = mScroller.getCurrY(); - mScroller.abortAnimation(); - final int newX = mScroller.getCurrX(); - final int newY = mScroller.getCurrY(); - mCallback.onViewPositionChanged(mCapturedView, newX, newY, newX - oldX, newY - oldY); - } - setDragState(STATE_IDLE); - } - - /** - * Animate the view <code>child</code> to the given (left, top) position. - * If this method returns true, the caller should invoke {@link #continueSettling(boolean)} - * on each subsequent frame to continue the motion until it returns false. If this method - * returns false there is no further work to do to complete the movement. - * <p> - * <p>This operation does not count as a capture event, though {@link #getCapturedView()} - * will still report the sliding view while the slide is in progress.</p> - * - * @param child Child view to capture and animate - * @param finalLeft Final left position of child - * @param finalTop Final top position of child - * @return true if animation should continue through {@link #continueSettling(boolean)} calls - */ - public boolean smoothSlideViewTo(View child, int finalLeft, int finalTop) { - mCapturedView = child; - mActivePointerId = INVALID_POINTER; - - return forceSettleCapturedViewAt(finalLeft, finalTop, 0, 0); - } - - /** - * Settle the captured view at the given (left, top) position. - * The appropriate velocity from prior motion will be taken into account. - * If this method returns true, the caller should invoke {@link #continueSettling(boolean)} - * on each subsequent frame to continue the motion until it returns false. If this method - * returns false there is no further work to do to complete the movement. - * - * @param finalLeft Settled left edge position for the captured view - * @param finalTop Settled top edge position for the captured view - * @return true if animation should continue through {@link #continueSettling(boolean)} calls - */ - public boolean settleCapturedViewAt(int finalLeft, int finalTop) { - if (!mReleaseInProgress) { - throw new IllegalStateException("Cannot settleCapturedViewAt outside of a call to " + - "Callback#onViewReleased"); - } - - return forceSettleCapturedViewAt(finalLeft, finalTop, - (int) VelocityTrackerCompat.getXVelocity(mVelocityTracker, mActivePointerId), - (int) VelocityTrackerCompat.getYVelocity(mVelocityTracker, mActivePointerId)); - } - - /** - * Settle the captured view at the given (left, top) position. - * - * @param finalLeft Target left position for the captured view - * @param finalTop Target top position for the captured view - * @param xvel Horizontal velocity - * @param yvel Vertical velocity - * @return true if animation should continue through {@link #continueSettling(boolean)} calls - */ - private boolean forceSettleCapturedViewAt(int finalLeft, int finalTop, int xvel, int yvel) { - final int startLeft = mCapturedView.getLeft(); - final int startTop = mCapturedView.getTop(); - final int dx = finalLeft - startLeft; - final int dy = finalTop - startTop; - - if (dx == 0 && dy == 0) { - // Nothing to do. Send callbacks, be done. - mScroller.abortAnimation(); - setDragState(STATE_IDLE); - return false; - } - - final int duration = computeSettleDuration(mCapturedView, dx, dy, xvel, yvel); - mScroller.startScroll(startLeft, startTop, dx, dy, duration); - - setDragState(STATE_SETTLING); - return true; - } - - private int computeSettleDuration(View child, int dx, int dy, int xvel, int yvel) { - xvel = clampMag(xvel, (int) mMinVelocity, (int) mMaxVelocity); - yvel = clampMag(yvel, (int) mMinVelocity, (int) mMaxVelocity); - final int absDx = Math.abs(dx); - final int absDy = Math.abs(dy); - final int absXVel = Math.abs(xvel); - final int absYVel = Math.abs(yvel); - final int addedVel = absXVel + absYVel; - final int addedDistance = absDx + absDy; - - final float xweight = xvel != 0 ? (float) absXVel / addedVel : - (float) absDx / addedDistance; - final float yweight = yvel != 0 ? (float) absYVel / addedVel : - (float) absDy / addedDistance; - - int xduration = computeAxisDuration(dx, xvel, mCallback.getViewHorizontalDragRange(child)); - int yduration = computeAxisDuration(dy, yvel, mCallback.getViewVerticalDragRange(child)); - - return (int) (xduration * xweight + yduration * yweight); - } - - private int computeAxisDuration(int delta, int velocity, int motionRange) { - if (delta == 0) { - return 0; - } - - final int width = mParentView.getWidth(); - final int halfWidth = width / 2; - final float distanceRatio = Math.min(1f, (float) Math.abs(delta) / width); - final float distance = halfWidth + halfWidth * - distanceInfluenceForSnapDuration(distanceRatio); - - int duration; - velocity = Math.abs(velocity); - if (velocity > 0) { - duration = 4 * Math.round(1000 * Math.abs(distance / velocity)); - } else { - final float range = (float) Math.abs(delta) / motionRange; - duration = (int) ((range + 1) * BASE_SETTLE_DURATION); - } - return Math.min(duration, MAX_SETTLE_DURATION); - } - - /** - * Clamp the magnitude of value for absMin and absMax. - * If the value is below the minimum, it will be clamped to zero. - * If the value is above the maximum, it will be clamped to the maximum. - * - * @param value Value to clamp - * @param absMin Absolute value of the minimum significant value to return - * @param absMax Absolute value of the maximum value to return - * @return The clamped value with the same sign as <code>value</code> - */ - private int clampMag(int value, int absMin, int absMax) { - final int absValue = Math.abs(value); - if (absValue < absMin) return 0; - if (absValue > absMax) return value > 0 ? absMax : -absMax; - return value; - } - - /** - * Clamp the magnitude of value for absMin and absMax. - * If the value is below the minimum, it will be clamped to zero. - * If the value is above the maximum, it will be clamped to the maximum. - * - * @param value Value to clamp - * @param absMin Absolute value of the minimum significant value to return - * @param absMax Absolute value of the maximum value to return - * @return The clamped value with the same sign as <code>value</code> - */ - private float clampMag(float value, float absMin, float absMax) { - final float absValue = Math.abs(value); - if (absValue < absMin) return 0; - if (absValue > absMax) return value > 0 ? absMax : -absMax; - return value; - } - - private float distanceInfluenceForSnapDuration(float f) { - f -= 0.5f; // center the values about 0. - f *= 0.3f * Math.PI / 2.0f; - return (float) Math.sin(f); - } - - /** - * Settle the captured view based on standard free-moving fling behavior. - * The caller should invoke {@link #continueSettling(boolean)} on each subsequent frame - * to continue the motion until it returns false. - * - * @param minLeft Minimum X position for the view's left edge - * @param minTop Minimum Y position for the view's top edge - * @param maxLeft Maximum X position for the view's left edge - * @param maxTop Maximum Y position for the view's top edge - */ - public void flingCapturedView(int minLeft, int minTop, int maxLeft, int maxTop) { - if (!mReleaseInProgress) { - throw new IllegalStateException("Cannot flingCapturedView outside of a call to " + - "Callback#onViewReleased"); - } - - mScroller.fling(mCapturedView.getLeft(), mCapturedView.getTop(), - (int) VelocityTrackerCompat.getXVelocity(mVelocityTracker, mActivePointerId), - (int) VelocityTrackerCompat.getYVelocity(mVelocityTracker, mActivePointerId), - minLeft, maxLeft, minTop, maxTop); - - setDragState(STATE_SETTLING); - } - - /** - * Move the captured settling view by the appropriate amount for the current time. - * If <code>continueSettling</code> returns true, the caller should call it again - * on the next frame to continue. - * - * @param deferCallbacks true if state callbacks should be deferred via posted message. - * Set this to true if you are calling this method from - * {@link android.view.View#computeScroll()} or similar methods - * invoked as part of layout or drawing. - * @return true if settle is still in progress - */ - public boolean continueSettling(boolean deferCallbacks) { - // Make sure, there is a captured view - if (mCapturedView == null) { - return false; - } - if (mDragState == STATE_SETTLING) { - boolean keepGoing = mScroller.computeScrollOffset(); - final int x = mScroller.getCurrX(); - final int y = mScroller.getCurrY(); - final int dx = x - mCapturedView.getLeft(); - final int dy = y - mCapturedView.getTop(); - - if (!keepGoing && dy != 0) { //fix #525 - //Invalid drag state - mCapturedView.setTop(0); - return true; - } - - if (dx != 0) { - mCapturedView.offsetLeftAndRight(dx); - } - if (dy != 0) { - mCapturedView.offsetTopAndBottom(dy); - } - - if (dx != 0 || dy != 0) { - mCallback.onViewPositionChanged(mCapturedView, x, y, dx, dy); - } - - if (keepGoing && x == mScroller.getFinalX() && y == mScroller.getFinalY()) { - // Close enough. The interpolator/scroller might think we're still moving - // but the user sure doesn't. - mScroller.abortAnimation(); - keepGoing = mScroller.isFinished(); - } - - if (!keepGoing) { - if (deferCallbacks) { - mParentView.post(mSetIdleRunnable); - } else { - setDragState(STATE_IDLE); - } - } - } - - return mDragState == STATE_SETTLING; - } - - /** - * Like all callback events this must happen on the UI thread, but release - * involves some extra semantics. During a release (mReleaseInProgress) - * is the only time it is valid to call {@link #settleCapturedViewAt(int, int)} - * or {@link #flingCapturedView(int, int, int, int)}. - */ - private void dispatchViewReleased(float xvel, float yvel) { - mReleaseInProgress = true; - mCallback.onViewReleased(mCapturedView, xvel, yvel); - mReleaseInProgress = false; - - if (mDragState == STATE_DRAGGING) { - // onViewReleased didn't call a method that would have changed this. Go idle. - setDragState(STATE_IDLE); - } - } - - private void clearMotionHistory() { - if (mInitialMotionX == null) { - return; - } - Arrays.fill(mInitialMotionX, 0); - Arrays.fill(mInitialMotionY, 0); - Arrays.fill(mLastMotionX, 0); - Arrays.fill(mLastMotionY, 0); - Arrays.fill(mInitialEdgesTouched, 0); - Arrays.fill(mEdgeDragsInProgress, 0); - Arrays.fill(mEdgeDragsLocked, 0); - mPointersDown = 0; - } - - private void clearMotionHistory(int pointerId) { - if (mInitialMotionX == null || mInitialMotionX.length <= pointerId) { - return; - } - mInitialMotionX[pointerId] = 0; - mInitialMotionY[pointerId] = 0; - mLastMotionX[pointerId] = 0; - mLastMotionY[pointerId] = 0; - mInitialEdgesTouched[pointerId] = 0; - mEdgeDragsInProgress[pointerId] = 0; - mEdgeDragsLocked[pointerId] = 0; - mPointersDown &= ~(1 << pointerId); - } - - private void ensureMotionHistorySizeForId(int pointerId) { - if (mInitialMotionX == null || mInitialMotionX.length <= pointerId) { - float[] imx = new float[pointerId + 1]; - float[] imy = new float[pointerId + 1]; - float[] lmx = new float[pointerId + 1]; - float[] lmy = new float[pointerId + 1]; - int[] iit = new int[pointerId + 1]; - int[] edip = new int[pointerId + 1]; - int[] edl = new int[pointerId + 1]; - - if (mInitialMotionX != null) { - System.arraycopy(mInitialMotionX, 0, imx, 0, mInitialMotionX.length); - System.arraycopy(mInitialMotionY, 0, imy, 0, mInitialMotionY.length); - System.arraycopy(mLastMotionX, 0, lmx, 0, mLastMotionX.length); - System.arraycopy(mLastMotionY, 0, lmy, 0, mLastMotionY.length); - System.arraycopy(mInitialEdgesTouched, 0, iit, 0, mInitialEdgesTouched.length); - System.arraycopy(mEdgeDragsInProgress, 0, edip, 0, mEdgeDragsInProgress.length); - System.arraycopy(mEdgeDragsLocked, 0, edl, 0, mEdgeDragsLocked.length); - } - - mInitialMotionX = imx; - mInitialMotionY = imy; - mLastMotionX = lmx; - mLastMotionY = lmy; - mInitialEdgesTouched = iit; - mEdgeDragsInProgress = edip; - mEdgeDragsLocked = edl; - } - } - - private void saveInitialMotion(float x, float y, int pointerId) { - ensureMotionHistorySizeForId(pointerId); - mInitialMotionX[pointerId] = mLastMotionX[pointerId] = x; - mInitialMotionY[pointerId] = mLastMotionY[pointerId] = y; - mInitialEdgesTouched[pointerId] = getEdgesTouched((int) x, (int) y); - mPointersDown |= 1 << pointerId; - } - - private void saveLastMotion(MotionEvent ev) { - final int pointerCount = MotionEventCompat.getPointerCount(ev); - for (int i = 0; i < pointerCount; i++) { - final int pointerId = MotionEventCompat.getPointerId(ev, i); - final float x = MotionEventCompat.getX(ev, i); - final float y = MotionEventCompat.getY(ev, i); - // Sometimes we can try and save last motion for a pointer never recorded in initial motion. In this case we just discard it. - if (mLastMotionX != null && mLastMotionY != null - && mLastMotionX.length > pointerId && mLastMotionY.length > pointerId) { - mLastMotionX[pointerId] = x; - mLastMotionY[pointerId] = y; - } - } - } - - /** - * Check if the given pointer ID represents a pointer that is currently down (to the best - * of the ViewDragHelper's knowledge). - * <p> - * <p>The state used to report this information is populated by the methods - * {@link #shouldInterceptTouchEvent(android.view.MotionEvent)} or - * {@link #processTouchEvent(android.view.MotionEvent)}. If one of these methods has not - * been called for all relevant MotionEvents to track, the information reported - * by this method may be stale or incorrect.</p> - * - * @param pointerId pointer ID to check; corresponds to IDs provided by MotionEvent - * @return true if the pointer with the given ID is still down - */ - public boolean isPointerDown(int pointerId) { - return (mPointersDown & 1 << pointerId) != 0; - } - - void setDragState(int state) { - if (mDragState != state) { - mDragState = state; - mCallback.onViewDragStateChanged(state); - if (mDragState == STATE_IDLE) { - mCapturedView = null; - } - } - } - - /** - * Attempt to capture the view with the given pointer ID. The callback will be involved. - * This will put us into the "dragging" state. If we've already captured this view with - * this pointer this method will immediately return true without consulting the callback. - * - * @param toCapture View to capture - * @param pointerId Pointer to capture with - * @return true if capture was successful - */ - boolean tryCaptureViewForDrag(View toCapture, int pointerId) { - if (toCapture == mCapturedView && mActivePointerId == pointerId) { - // Already done! - return true; - } - if (toCapture != null && mCallback.tryCaptureView(toCapture, pointerId)) { - mActivePointerId = pointerId; - captureChildView(toCapture, pointerId); - return true; - } - return false; - } - - /** - * Tests scrollability within child views of v given a delta of dx. - * - * @param v View to test for horizontal scrollability - * @param checkV Whether the view v passed should itself be checked for scrollability (true), - * or just its children (false). - * @param dx Delta scrolled in pixels along the X axis - * @param dy Delta scrolled in pixels along the Y axis - * @param x X coordinate of the active touch point - * @param y Y coordinate of the active touch point - * @return true if child views of v can be scrolled by delta of dx. - */ - protected boolean canScroll(View v, boolean checkV, int dx, int dy, int x, int y) { - if (v instanceof ViewGroup) { - final ViewGroup group = (ViewGroup) v; - final int scrollX = v.getScrollX(); - final int scrollY = v.getScrollY(); - final int count = group.getChildCount(); - // Count backwards - let topmost views consume scroll distance first. - for (int i = count - 1; i >= 0; i--) { - // TODO: Add versioned support here for transformed views. - // This will not work for transformed views in Honeycomb+ - final View child = group.getChildAt(i); - if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() && - y + scrollY >= child.getTop() && y + scrollY < child.getBottom() && - canScroll(child, true, dx, dy, x + scrollX - child.getLeft(), - y + scrollY - child.getTop())) { - return true; - } - } - } - - return checkV && (ViewCompat.canScrollHorizontally(v, -dx) || - ViewCompat.canScrollVertically(v, -dy)); - } - - /** - * Check if this event as provided to the parent view's onInterceptTouchEvent should - * cause the parent to intercept the touch event stream. - * - * @param ev MotionEvent provided to onInterceptTouchEvent - * @return true if the parent view should return true from onInterceptTouchEvent - */ - public boolean shouldInterceptTouchEvent(MotionEvent ev) { - final int action = MotionEventCompat.getActionMasked(ev); - final int actionIndex = MotionEventCompat.getActionIndex(ev); - - if (action == MotionEvent.ACTION_DOWN) { - // Reset things for a new event stream, just in case we didn't get - // the whole previous stream. - cancel(); - } - - if (mVelocityTracker == null) { - mVelocityTracker = VelocityTracker.obtain(); - } - mVelocityTracker.addMovement(ev); - - switch (action) { - case MotionEvent.ACTION_DOWN: { - final float x = ev.getX(); - final float y = ev.getY(); - final int pointerId = MotionEventCompat.getPointerId(ev, 0); - saveInitialMotion(x, y, pointerId); - - final View toCapture = findTopChildUnder((int) x, (int) y); - - // Catch a settling view if possible. - if (toCapture == mCapturedView && mDragState == STATE_SETTLING) { - tryCaptureViewForDrag(toCapture, pointerId); - } - - final int edgesTouched = mInitialEdgesTouched[pointerId]; - if ((edgesTouched & mTrackingEdges) != 0) { - mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId); - } - break; - } - - case MotionEventCompat.ACTION_POINTER_DOWN: { - final int pointerId = MotionEventCompat.getPointerId(ev, actionIndex); - final float x = MotionEventCompat.getX(ev, actionIndex); - final float y = MotionEventCompat.getY(ev, actionIndex); - - saveInitialMotion(x, y, pointerId); - - // A ViewDragHelper can only manipulate one view at a time. - if (mDragState == STATE_IDLE) { - final int edgesTouched = mInitialEdgesTouched[pointerId]; - if ((edgesTouched & mTrackingEdges) != 0) { - mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId); - } - } else if (mDragState == STATE_SETTLING) { - // Catch a settling view if possible. - final View toCapture = findTopChildUnder((int) x, (int) y); - if (toCapture == mCapturedView) { - tryCaptureViewForDrag(toCapture, pointerId); - } - } - break; - } - - case MotionEvent.ACTION_MOVE: { - // First to cross a touch slop over a draggable view wins. Also report edge drags. - final int pointerCount = MotionEventCompat.getPointerCount(ev); - for (int i = 0; i < pointerCount && mInitialMotionX != null && mInitialMotionY != null; i++) { - final int pointerId = MotionEventCompat.getPointerId(ev, i); - if (pointerId >= mInitialMotionX.length || pointerId >= mInitialMotionY.length) { - continue; - } - final float x = MotionEventCompat.getX(ev, i); - final float y = MotionEventCompat.getY(ev, i); - final float dx = x - mInitialMotionX[pointerId]; - final float dy = y - mInitialMotionY[pointerId]; - - reportNewEdgeDrags(dx, dy, pointerId); - if (mDragState == STATE_DRAGGING) { - // Callback might have started an edge drag - break; - } - - final View toCapture = findTopChildUnder((int) mInitialMotionX[pointerId], (int) mInitialMotionY[pointerId]); - if (toCapture != null && checkTouchSlop(toCapture, dx, dy) && - tryCaptureViewForDrag(toCapture, pointerId)) { - break; - } - } - saveLastMotion(ev); - break; - } - - case MotionEventCompat.ACTION_POINTER_UP: { - final int pointerId = MotionEventCompat.getPointerId(ev, actionIndex); - clearMotionHistory(pointerId); - break; - } - - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: { - cancel(); - break; - } - } - - return mDragState == STATE_DRAGGING; - } - - /** - * Process a touch event received by the parent view. This method will dispatch callback events - * as needed before returning. The parent view's onTouchEvent implementation should call this. - * - * @param ev The touch event received by the parent view - */ - public void processTouchEvent(MotionEvent ev) { - final int action = MotionEventCompat.getActionMasked(ev); - final int actionIndex = MotionEventCompat.getActionIndex(ev); - - if (action == MotionEvent.ACTION_DOWN) { - // Reset things for a new event stream, just in case we didn't get - // the whole previous stream. - cancel(); - } - - if (mVelocityTracker == null) { - mVelocityTracker = VelocityTracker.obtain(); - } - mVelocityTracker.addMovement(ev); - - switch (action) { - case MotionEvent.ACTION_DOWN: { - final float x = ev.getX(); - final float y = ev.getY(); - final int pointerId = MotionEventCompat.getPointerId(ev, 0); - final View toCapture = findTopChildUnder((int) x, (int) y); - - saveInitialMotion(x, y, pointerId); - - // Since the parent is already directly processing this touch event, - // there is no reason to delay for a slop before dragging. - // Start immediately if possible. - tryCaptureViewForDrag(toCapture, pointerId); - - final int edgesTouched = mInitialEdgesTouched[pointerId]; - if ((edgesTouched & mTrackingEdges) != 0) { - mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId); - } - break; - } - - case MotionEventCompat.ACTION_POINTER_DOWN: { - final int pointerId = MotionEventCompat.getPointerId(ev, actionIndex); - final float x = MotionEventCompat.getX(ev, actionIndex); - final float y = MotionEventCompat.getY(ev, actionIndex); - - saveInitialMotion(x, y, pointerId); - - // A ViewDragHelper can only manipulate one view at a time. - if (mDragState == STATE_IDLE) { - // If we're idle we can do anything! Treat it like a normal down event. - - final View toCapture = findTopChildUnder((int) x, (int) y); - tryCaptureViewForDrag(toCapture, pointerId); - - final int edgesTouched = mInitialEdgesTouched[pointerId]; - if ((edgesTouched & mTrackingEdges) != 0) { - mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId); - } - } else if (isCapturedViewUnder((int) x, (int) y)) { - // We're still tracking a captured view. If the same view is under this - // point, we'll swap to controlling it with this pointer instead. - // (This will still work if we're "catching" a settling view.) - - tryCaptureViewForDrag(mCapturedView, pointerId); - } - break; - } - - case MotionEvent.ACTION_MOVE: { - if (mDragState == STATE_DRAGGING) { - final int index = MotionEventCompat.findPointerIndex(ev, mActivePointerId); - final float x = MotionEventCompat.getX(ev, index); - final float y = MotionEventCompat.getY(ev, index); - final int idx = (int) (x - mLastMotionX[mActivePointerId]); - final int idy = (int) (y - mLastMotionY[mActivePointerId]); - - dragTo(mCapturedView.getLeft() + idx, mCapturedView.getTop() + idy, idx, idy); - - saveLastMotion(ev); - } else { - // Check to see if any pointer is now over a draggable view. - final int pointerCount = MotionEventCompat.getPointerCount(ev); - for (int i = 0; i < pointerCount; i++) { - final int pointerId = MotionEventCompat.getPointerId(ev, i); - final float x = MotionEventCompat.getX(ev, i); - final float y = MotionEventCompat.getY(ev, i); - final float dx = x - mInitialMotionX[pointerId]; - final float dy = y - mInitialMotionY[pointerId]; - - reportNewEdgeDrags(dx, dy, pointerId); - if (mDragState == STATE_DRAGGING) { - // Callback might have started an edge drag. - break; - } - - final View toCapture = findTopChildUnder((int) mInitialMotionX[pointerId], (int) mInitialMotionY[pointerId]); - if (checkTouchSlop(toCapture, dx, dy) && - tryCaptureViewForDrag(toCapture, pointerId)) { - break; - } - } - saveLastMotion(ev); - } - break; - } - - case MotionEventCompat.ACTION_POINTER_UP: { - final int pointerId = MotionEventCompat.getPointerId(ev, actionIndex); - if (mDragState == STATE_DRAGGING && pointerId == mActivePointerId) { - // Try to find another pointer that's still holding on to the captured view. - int newActivePointer = INVALID_POINTER; - final int pointerCount = MotionEventCompat.getPointerCount(ev); - for (int i = 0; i < pointerCount; i++) { - final int id = MotionEventCompat.getPointerId(ev, i); - if (id == mActivePointerId) { - // This one's going away, skip. - continue; - } - - final float x = MotionEventCompat.getX(ev, i); - final float y = MotionEventCompat.getY(ev, i); - if (findTopChildUnder((int) x, (int) y) == mCapturedView && - tryCaptureViewForDrag(mCapturedView, id)) { - newActivePointer = mActivePointerId; - break; - } - } - - if (newActivePointer == INVALID_POINTER) { - // We didn't find another pointer still touching the view, release it. - releaseViewForPointerUp(); - } - } - clearMotionHistory(pointerId); - break; - } - - case MotionEvent.ACTION_UP: { - if (mDragState == STATE_DRAGGING) { - releaseViewForPointerUp(); - } - cancel(); - break; - } - - case MotionEvent.ACTION_CANCEL: { - if (mDragState == STATE_DRAGGING) { - dispatchViewReleased(0, 0); - } - cancel(); - break; - } - } - } - - private void reportNewEdgeDrags(float dx, float dy, int pointerId) { - int dragsStarted = 0; - if (checkNewEdgeDrag(dx, dy, pointerId, EDGE_LEFT)) { - dragsStarted |= EDGE_LEFT; - } - if (checkNewEdgeDrag(dy, dx, pointerId, EDGE_TOP)) { - dragsStarted |= EDGE_TOP; - } - if (checkNewEdgeDrag(dx, dy, pointerId, EDGE_RIGHT)) { - dragsStarted |= EDGE_RIGHT; - } - if (checkNewEdgeDrag(dy, dx, pointerId, EDGE_BOTTOM)) { - dragsStarted |= EDGE_BOTTOM; - } - - if (dragsStarted != 0) { - mEdgeDragsInProgress[pointerId] |= dragsStarted; - mCallback.onEdgeDragStarted(dragsStarted, pointerId); - } - } - - private boolean checkNewEdgeDrag(float delta, float odelta, int pointerId, int edge) { - final float absDelta = Math.abs(delta); - final float absODelta = Math.abs(odelta); - - if ((mInitialEdgesTouched[pointerId] & edge) != edge || (mTrackingEdges & edge) == 0 || - (mEdgeDragsLocked[pointerId] & edge) == edge || - (mEdgeDragsInProgress[pointerId] & edge) == edge || - (absDelta <= mTouchSlop && absODelta <= mTouchSlop)) { - return false; - } - if (absDelta < absODelta * 0.5f && mCallback.onEdgeLock(edge)) { - mEdgeDragsLocked[pointerId] |= edge; - return false; - } - return (mEdgeDragsInProgress[pointerId] & edge) == 0 && absDelta > mTouchSlop; - } - - /** - * Check if we've crossed a reasonable touch slop for the given child view. - * If the child cannot be dragged along the horizontal or vertical axis, motion - * along that axis will not count toward the slop check. - * - * @param child Child to check - * @param dx Motion since initial position along X axis - * @param dy Motion since initial position along Y axis - * @return true if the touch slop has been crossed - */ - private boolean checkTouchSlop(View child, float dx, float dy) { - if (child == null) { - return false; - } - final boolean checkHorizontal = mCallback.getViewHorizontalDragRange(child) > 0; - final boolean checkVertical = mCallback.getViewVerticalDragRange(child) > 0; - - if (checkHorizontal && checkVertical) { - return dx * dx + dy * dy > mTouchSlop * mTouchSlop; - } else if (checkHorizontal) { - return Math.abs(dx) > mTouchSlop; - } else if (checkVertical) { - return Math.abs(dy) > mTouchSlop; - } - return false; - } - - /** - * Check if any pointer tracked in the current gesture has crossed - * the required slop threshold. - * <p> - * <p>This depends on internal state populated by - * {@link #shouldInterceptTouchEvent(android.view.MotionEvent)} or - * {@link #processTouchEvent(android.view.MotionEvent)}. You should only rely on - * the results of this method after all currently available touch data - * has been provided to one of these two methods.</p> - * - * @param directions Combination of direction flags, see {@link #DIRECTION_HORIZONTAL}, - * {@link #DIRECTION_VERTICAL}, {@link #DIRECTION_ALL} - * @return true if the slop threshold has been crossed, false otherwise - */ - public boolean checkTouchSlop(int directions) { - final int count = mInitialMotionX.length; - for (int i = 0; i < count; i++) { - if (checkTouchSlop(directions, i)) { - return true; - } - } - return false; - } - - /** - * Check if the specified pointer tracked in the current gesture has crossed - * the required slop threshold. - * <p> - * <p>This depends on internal state populated by - * {@link #shouldInterceptTouchEvent(android.view.MotionEvent)} or - * {@link #processTouchEvent(android.view.MotionEvent)}. You should only rely on - * the results of this method after all currently available touch data - * has been provided to one of these two methods.</p> - * - * @param directions Combination of direction flags, see {@link #DIRECTION_HORIZONTAL}, - * {@link #DIRECTION_VERTICAL}, {@link #DIRECTION_ALL} - * @param pointerId ID of the pointer to slop check as specified by MotionEvent - * @return true if the slop threshold has been crossed, false otherwise - */ - public boolean checkTouchSlop(int directions, int pointerId) { - if (!isPointerDown(pointerId)) { - return false; - } - - final boolean checkHorizontal = (directions & DIRECTION_HORIZONTAL) == DIRECTION_HORIZONTAL; - final boolean checkVertical = (directions & DIRECTION_VERTICAL) == DIRECTION_VERTICAL; - - final float dx = mLastMotionX[pointerId] - mInitialMotionX[pointerId]; - final float dy = mLastMotionY[pointerId] - mInitialMotionY[pointerId]; - - if (checkHorizontal && checkVertical) { - return dx * dx + dy * dy > mTouchSlop * mTouchSlop; - } else if (checkHorizontal) { - return Math.abs(dx) > mTouchSlop; - } else if (checkVertical) { - return Math.abs(dy) > mTouchSlop; - } - return false; - } - - /** - * Check if any of the edges specified were initially touched in the currently active gesture. - * If there is no currently active gesture this method will return false. - * - * @param edges Edges to check for an initial edge touch. See {@link #EDGE_LEFT}, - * {@link #EDGE_TOP}, {@link #EDGE_RIGHT}, {@link #EDGE_BOTTOM} and - * {@link #EDGE_ALL} - * @return true if any of the edges specified were initially touched in the current gesture - */ - public boolean isEdgeTouched(int edges) { - final int count = mInitialEdgesTouched.length; - for (int i = 0; i < count; i++) { - if (isEdgeTouched(edges, i)) { - return true; - } - } - return false; - } - - /** - * Check if any of the edges specified were initially touched by the pointer with - * the specified ID. If there is no currently active gesture or if there is no pointer with - * the given ID currently down this method will return false. - * - * @param edges Edges to check for an initial edge touch. See {@link #EDGE_LEFT}, - * {@link #EDGE_TOP}, {@link #EDGE_RIGHT}, {@link #EDGE_BOTTOM} and - * {@link #EDGE_ALL} - * @return true if any of the edges specified were initially touched in the current gesture - */ - public boolean isEdgeTouched(int edges, int pointerId) { - return isPointerDown(pointerId) && (mInitialEdgesTouched[pointerId] & edges) != 0; - } - - public boolean isDragging() { - return mDragState == STATE_DRAGGING; - } - - private void releaseViewForPointerUp() { - mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity); - final float xvel = clampMag( - VelocityTrackerCompat.getXVelocity(mVelocityTracker, mActivePointerId), - mMinVelocity, mMaxVelocity); - final float yvel = clampMag( - VelocityTrackerCompat.getYVelocity(mVelocityTracker, mActivePointerId), - mMinVelocity, mMaxVelocity); - dispatchViewReleased(xvel, yvel); - } - - private void dragTo(int left, int top, int dx, int dy) { - int clampedX = left; - int clampedY = top; - final int oldLeft = mCapturedView.getLeft(); - final int oldTop = mCapturedView.getTop(); - if (dx != 0) { - clampedX = mCallback.clampViewPositionHorizontal(mCapturedView, left, dx); - mCapturedView.offsetLeftAndRight(clampedX - oldLeft); - } - if (dy != 0) { - clampedY = mCallback.clampViewPositionVertical(mCapturedView, top, dy); - mCapturedView.offsetTopAndBottom(clampedY - oldTop); - } - - if (dx != 0 || dy != 0) { - final int clampedDx = clampedX - oldLeft; - final int clampedDy = clampedY - oldTop; - mCallback.onViewPositionChanged(mCapturedView, clampedX, clampedY, - clampedDx, clampedDy); - } - } - - /** - * Determine if the currently captured view is under the given point in the - * parent view's coordinate system. If there is no captured view this method - * will return false. - * - * @param x X position to test in the parent's coordinate system - * @param y Y position to test in the parent's coordinate system - * @return true if the captured view is under the given point, false otherwise - */ - public boolean isCapturedViewUnder(int x, int y) { - return isViewUnder(mCapturedView, x, y); - } - - /** - * Determine if the supplied view is under the given point in the - * parent view's coordinate system. - * - * @param view Child view of the parent to hit test - * @param x X position to test in the parent's coordinate system - * @param y Y position to test in the parent's coordinate system - * @return true if the supplied view is under the given point, false otherwise - */ - public boolean isViewUnder(View view, int x, int y) { - if (view == null) { - return false; - } - return x >= view.getLeft() && - x < view.getRight() && - y >= view.getTop() && - y < view.getBottom(); - } - - /** - * Find the topmost child under the given point within the parent view's coordinate system. - * The child order is determined using {@link Callback#getOrderedChildIndex(int)}. - * - * @param x X position to test in the parent's coordinate system - * @param y Y position to test in the parent's coordinate system - * @return The topmost child view under (x, y) or null if none found. - */ - public View findTopChildUnder(int x, int y) { - final int childCount = mParentView.getChildCount(); - for (int i = childCount - 1; i >= 0; i--) { - final View child = mParentView.getChildAt(mCallback.getOrderedChildIndex(i)); - if (x >= child.getLeft() && x < child.getRight() && - y >= child.getTop() && y < child.getBottom()) { - return child; - } - } - return null; - } - - private int getEdgesTouched(int x, int y) { - int result = 0; - - if (x < mParentView.getLeft() + mEdgeSize) result |= EDGE_LEFT; - if (y < mParentView.getTop() + mEdgeSize) result |= EDGE_TOP; - if (x > mParentView.getRight() - mEdgeSize) result |= EDGE_RIGHT; - if (y > mParentView.getBottom() - mEdgeSize) result |= EDGE_BOTTOM; - - return result; - } - - /** - * A Callback is used as a communication channel with the ViewDragHelper back to the - * parent view using it. <code>on*</code>methods are invoked on siginficant events and several - * accessor methods are expected to provide the ViewDragHelper with more information - * about the state of the parent view upon request. The callback also makes decisions - * governing the range and draggability of child views. - */ - public static abstract class Callback { - /** - * Called when the drag state changes. See the <code>STATE_*</code> constants - * for more information. - * - * @param state The new drag state - * @see #STATE_IDLE - * @see #STATE_DRAGGING - * @see #STATE_SETTLING - */ - public void onViewDragStateChanged(int state) { - } - - /** - * Called when the captured view's position changes as the result of a drag or settle. - * - * @param changedView View whose position changed - * @param left New X coordinate of the left edge of the view - * @param top New Y coordinate of the top edge of the view - * @param dx Change in X position from the last call - * @param dy Change in Y position from the last call - */ - public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) { - } - - /** - * Called when a child view is captured for dragging or settling. The ID of the pointer - * currently dragging the captured view is supplied. If activePointerId is - * identified as {@link #INVALID_POINTER} the capture is programmatic instead of - * pointer-initiated. - * - * @param capturedChild Child view that was captured - * @param activePointerId Pointer id tracking the child capture - */ - public void onViewCaptured(View capturedChild, int activePointerId) { - } - - /** - * Called when the child view is no longer being actively dragged. - * The fling velocity is also supplied, if relevant. The velocity values may - * be clamped to system minimums or maximums. - * <p> - * <p>Calling code may decide to fling or otherwise release the view to let it - * settle into place. It should do so using {@link #settleCapturedViewAt(int, int)} - * or {@link #flingCapturedView(int, int, int, int)}. If the Callback invokes - * one of these methods, the ViewDragHelper will enter {@link #STATE_SETTLING} - * and the view capture will not fully end until it comes to a complete stop. - * If neither of these methods is invoked before <code>onViewReleased</code> returns, - * the view will stop in place and the ViewDragHelper will return to - * {@link #STATE_IDLE}.</p> - * - * @param releasedChild The captured child view now being released - * @param xvel X velocity of the pointer as it left the screen in pixels per second. - * @param yvel Y velocity of the pointer as it left the screen in pixels per second. - */ - public void onViewReleased(View releasedChild, float xvel, float yvel) { - } - - /** - * Called when one of the subscribed edges in the parent view has been touched - * by the user while no child view is currently captured. - * - * @param edgeFlags A combination of edge flags describing the edge(s) currently touched - * @param pointerId ID of the pointer touching the described edge(s) - * @see #EDGE_LEFT - * @see #EDGE_TOP - * @see #EDGE_RIGHT - * @see #EDGE_BOTTOM - */ - public void onEdgeTouched(int edgeFlags, int pointerId) { - } - - /** - * Called when the given edge may become locked. This can happen if an edge drag - * was preliminarily rejected before beginning, but after {@link #onEdgeTouched(int, int)} - * was called. This method should return true to lock this edge or false to leave it - * unlocked. The default behavior is to leave edges unlocked. - * - * @param edgeFlags A combination of edge flags describing the edge(s) locked - * @return true to lock the edge, false to leave it unlocked - */ - public boolean onEdgeLock(int edgeFlags) { - return false; - } - - /** - * Called when the user has started a deliberate drag away from one - * of the subscribed edges in the parent view while no child view is currently captured. - * - * @param edgeFlags A combination of edge flags describing the edge(s) dragged - * @param pointerId ID of the pointer touching the described edge(s) - * @see #EDGE_LEFT - * @see #EDGE_TOP - * @see #EDGE_RIGHT - * @see #EDGE_BOTTOM - */ - public void onEdgeDragStarted(int edgeFlags, int pointerId) { - } - - /** - * Called to determine the Z-order of child views. - * - * @param index the ordered position to query for - * @return index of the view that should be ordered at position <code>index</code> - */ - public int getOrderedChildIndex(int index) { - return index; - } - - /** - * Return the magnitude of a draggable child view's horizontal range of motion in pixels. - * This method should return 0 for views that cannot move horizontally. - * - * @param child Child view to check - * @return range of horizontal motion in pixels - */ - public int getViewHorizontalDragRange(View child) { - return 0; - } - - /** - * Return the magnitude of a draggable child view's vertical range of motion in pixels. - * This method should return 0 for views that cannot move vertically. - * - * @param child Child view to check - * @return range of vertical motion in pixels - */ - public int getViewVerticalDragRange(View child) { - return 0; - } - - /** - * Called when the user's input indicates that they want to capture the given child view - * with the pointer indicated by pointerId. The callback should return true if the user - * is permitted to drag the given view with the indicated pointer. - * <p> - * <p>ViewDragHelper may call this method multiple times for the same view even if - * the view is already captured; this indicates that a new pointer is trying to take - * control of the view.</p> - * <p> - * <p>If this method returns true, a call to {@link #onViewCaptured(android.view.View, int)} - * will follow if the capture is successful.</p> - * - * @param child Child the user is attempting to capture - * @param pointerId ID of the pointer attempting the capture - * @return true if capture should be allowed, false otherwise - */ - public abstract boolean tryCaptureView(View child, int pointerId); - - /** - * Restrict the motion of the dragged child view along the horizontal axis. - * The default implementation does not allow horizontal motion; the extending - * class must override this method and provide the desired clamping. - * - * @param child Child view being dragged - * @param left Attempted motion along the X axis - * @param dx Proposed change in position for left - * @return The new clamped position for left - */ - public int clampViewPositionHorizontal(View child, int left, int dx) { - return 0; - } - - /** - * Restrict the motion of the dragged child view along the vertical axis. - * The default implementation does not allow vertical motion; the extending - * class must override this method and provide the desired clamping. - * - * @param child Child view being dragged - * @param top Attempted motion along the Y axis - * @param dy Proposed change in position for top - * @return The new clamped position for top - */ - public int clampViewPositionVertical(View child, int top, int dy) { - return 0; - } - } -} diff --git a/slidingpanel/src/main/res/values/attrs.xml b/slidingpanel/src/main/res/values/attrs.xml deleted file mode 100644 index 37c95b22956697b282a45e56f421ac7cdc1632f7..0000000000000000000000000000000000000000 --- a/slidingpanel/src/main/res/values/attrs.xml +++ /dev/null @@ -1,43 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?><!-- - Quasseldroid - Quassel client for Android - - Copyright (c) 2018 Janne Koschinski - Copyright (c) 2018 The Quassel Project - - This program is free software: you can redistribute it and/or modify it - under the terms of the GNU General Public License version 3 as published - by the Free Software Foundation. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program. If not, see <http://www.gnu.org/licenses/>. - --> - -<resources> - - <declare-styleable name="SlidingUpPanelLayout"> - <attr name="umanoPanelHeight" format="dimension" /> - <attr name="umanoShadowHeight" format="dimension" /> - <attr name="umanoParallaxOffset" format="dimension" /> - <attr name="umanoFadeColor" format="color" /> - <attr name="umanoFlingVelocity" format="integer" /> - <attr name="umanoDragView" format="reference" /> - <attr name="umanoAntiDragView" format="reference" /> - <attr name="umanoScrollableView" format="reference" /> - <attr name="umanoOverlay" format="boolean" /> - <attr name="umanoClipPanel" format="boolean" /> - <attr name="umanoAnchorPoint" format="float" /> - <attr name="umanoInitialState" format="enum"> - <enum name="expanded" value="0" /> - <enum name="collapsed" value="1" /> - <enum name="anchored" value="2" /> - <enum name="hidden" value="3" /> - </attr> - <attr name="umanoScrollInterpolator" format="reference" /> - </declare-styleable> - -</resources>