私はこのようなレイアウトを使用していることを考えると:
<Android.support.design.widget.CoordinatorLayout
Android:id="@+id/main_content"
xmlns:Android="http://schemas.Android.com/apk/res/Android"
xmlns:app="http://schemas.Android.com/apk/res-auto"
Android:layout_width="match_parent"
Android:layout_height="match_parent"
Android:fitsSystemWindows="true">
<Android.support.design.widget.AppBarLayout
Android:id="@+id/appbar"
Android:layout_width="match_parent"
Android:layout_height="@dimen/flexible_space_image_height"
Android:fitsSystemWindows="true"
Android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
<Android.support.design.widget.CollapsingToolbarLayout
Android:id="@+id/collapsing_toolbar"
Android:layout_width="match_parent"
Android:layout_height="match_parent"
Android:fitsSystemWindows="true"
app:expandedTitleMarginEnd="64dp"
app:expandedTitleMarginStart="48dp"
app:statusBarScrim="@Android:color/transparent"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<Android.support.v7.widget.Toolbar
Android:id="@+id/toolbar"
Android:layout_width="match_parent"
Android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
/>
</Android.support.design.widget.CollapsingToolbarLayout>
</Android.support.design.widget.AppBarLayout>
<Android.support.v7.widget.RecyclerView
Android:id="@+id/mainView"
Android:layout_width="match_parent"
Android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
/>
<Android.support.design.widget.FloatingActionButton
Android:layout_width="70dp"
Android:layout_height="70dp"
Android:layout_marginBottom="20dp"
app:fabSize="normal"
app:layout_anchor="@id/appbar"
app:layout_anchorGravity="bottom|center_horizontal"
/>
</Android.support.design.widget.CoordinatorLayout>
これはほぼ標準です Cheesesquare サンプル-FloatingActionButtonを除きます。FloatingActionButtonは約20dp上げます。
ただし、これは機能しません。マージンやパディングなどを使用しても、ボタンは常に次のようにアンカーのエッジの中央に配置されます。
意図したとおりにFABを20dp上げるにはどうすればよいですか?
CoordinatorLayout
とマージンに関するdesign-support-libに bugs が存在する可能性があるため、同じ「動作」を実装/コピーするFrameLayout
FABおよびpadding
を設定して効果をシミュレートできます。
パッケージスコープのクラスにアクセスする必要があるため、必ずAndroid.support.design.widget
パッケージに入れてください。
/*
* Copyright (C) 2015 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.
*/
import Android.annotation.TargetApi;
import Android.content.Context;
import Android.graphics.Rect;
import Android.os.Build;
import Android.support.v4.view.ViewCompat;
import Android.support.v4.view.ViewPropertyAnimatorListener;
import Android.util.AttributeSet;
import Android.view.View;
import Android.view.animation.Animation;
import Android.widget.FrameLayout;
import com.company.Android.R;
import Java.util.List;
@CoordinatorLayout.DefaultBehavior(FrameLayoutWithBehavior.Behavior.class)
public class FrameLayoutWithBehavior extends FrameLayout {
public FrameLayoutWithBehavior(final Context context) {
super(context);
}
public FrameLayoutWithBehavior(final Context context, final AttributeSet attrs) {
super(context, attrs);
}
public FrameLayoutWithBehavior(final Context context, final AttributeSet attrs, final int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@TargetApi(Build.VERSION_CODES.Lollipop)
public FrameLayoutWithBehavior(final Context context, final AttributeSet attrs, final int defStyleAttr, final int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public static class Behavior extends Android.support.design.widget.CoordinatorLayout.Behavior<FrameLayoutWithBehavior> {
private static final boolean SNACKBAR_BEHAVIOR_ENABLED;
private Rect mTmpRect;
private boolean mIsAnimatingOut;
private float mTranslationY;
public Behavior() {
}
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, FrameLayoutWithBehavior child, View dependency) {
return SNACKBAR_BEHAVIOR_ENABLED && dependency instanceof Snackbar.SnackbarLayout;
}
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, FrameLayoutWithBehavior child, View dependency) {
if (dependency instanceof Snackbar.SnackbarLayout) {
this.updateFabTranslationForSnackbar(parent, child, dependency);
} else if (dependency instanceof AppBarLayout) {
AppBarLayout appBarLayout = (AppBarLayout) dependency;
if (this.mTmpRect == null) {
this.mTmpRect = new Rect();
}
Rect rect = this.mTmpRect;
ViewGroupUtils.getDescendantRect(parent, dependency, rect);
if (rect.bottom <= appBarLayout.getMinimumHeightForVisibleOverlappingContent()) {
if (!this.mIsAnimatingOut && child.getVisibility() == VISIBLE) {
this.animateOut(child);
}
} else if (child.getVisibility() != VISIBLE) {
this.animateIn(child);
}
}
return false;
}
private void updateFabTranslationForSnackbar(CoordinatorLayout parent, FrameLayoutWithBehavior fab, View snackbar) {
float translationY = this.getFabTranslationYForSnackbar(parent, fab);
if (translationY != this.mTranslationY) {
ViewCompat.animate(fab)
.cancel();
if (Math.abs(translationY - this.mTranslationY) == (float) snackbar.getHeight()) {
ViewCompat.animate(fab)
.translationY(translationY)
.setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR)
.setListener((ViewPropertyAnimatorListener) null);
} else {
ViewCompat.setTranslationY(fab, translationY);
}
this.mTranslationY = translationY;
}
}
private float getFabTranslationYForSnackbar(CoordinatorLayout parent, FrameLayoutWithBehavior fab) {
float minOffset = 0.0F;
List dependencies = parent.getDependencies(fab);
int i = 0;
for (int z = dependencies.size(); i < z; ++i) {
View view = (View) dependencies.get(i);
if (view instanceof Snackbar.SnackbarLayout && parent.doViewsOverlap(fab, view)) {
minOffset = Math.min(minOffset, ViewCompat.getTranslationY(view) - (float) view.getHeight());
}
}
return minOffset;
}
private void animateIn(FrameLayoutWithBehavior button) {
button.setVisibility(View.VISIBLE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
ViewCompat.animate(button)
.scaleX(1.0F)
.scaleY(1.0F)
.alpha(1.0F)
.setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR)
.withLayer()
.setListener((ViewPropertyAnimatorListener) null)
.start();
} else {
Animation anim = Android.view.animation.AnimationUtils.loadAnimation(button.getContext(), R.anim.fab_in);
anim.setDuration(200L);
anim.setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR);
button.startAnimation(anim);
}
}
private void animateOut(final FrameLayoutWithBehavior button) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
ViewCompat.animate(button)
.scaleX(0.0F)
.scaleY(0.0F)
.alpha(0.0F)
.setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR)
.withLayer()
.setListener(new ViewPropertyAnimatorListener() {
public void onAnimationStart(View view) {
Behavior.this.mIsAnimatingOut = true;
}
public void onAnimationCancel(View view) {
Behavior.this.mIsAnimatingOut = false;
}
public void onAnimationEnd(View view) {
Behavior.this.mIsAnimatingOut = false;
view.setVisibility(View.GONE);
}
})
.start();
} else {
Animation anim = Android.view.animation.AnimationUtils.loadAnimation(button.getContext(), R.anim.fab_out);
anim.setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR);
anim.setDuration(200L);
anim.setAnimationListener(new AnimationUtils.AnimationListenerAdapter() {
public void onAnimationStart(Animation animation) {
Behavior.this.mIsAnimatingOut = true;
}
public void onAnimationEnd(Animation animation) {
Behavior.this.mIsAnimatingOut = false;
button.setVisibility(View.GONE);
}
});
button.startAnimation(anim);
}
}
static {
SNACKBAR_BEHAVIOR_ENABLED = Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;
}
}
}
パディングのある線形レイアウトに配置してみてください。
<LinearLayout
width=".."
height=".."
paddingBottom="20dp"
app:layout_anchor="@id/appbar"
app:layout_anchorGravity="bottom|center_horizontal">
<Android.support.design.widget.FloatingActionButton
Android:layout_width="70dp"
Android:layout_height="70dp"
app:fabSize="normal" />
</LinearLayout>
FloatingActionButton
をAppBar
の下に固定するには、次のようにします。
FloatingActionButton
を拡張し、offsetTopAndBottom
をオーバーライドします。
public class OffsetFloatingActionButton extends FloatingActionButton
{
public OffsetFloatingActionButton(Context context)
{
this(context, null);
}
public OffsetFloatingActionButton(Context context, AttributeSet attrs)
{
this(context, attrs, 0);
}
public OffsetFloatingActionButton(Context context, AttributeSet attrs, int defStyleAttr)
{
super(context, attrs, defStyleAttr);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom)
{
super.onLayout(changed, left, top, right, bottom);
ViewCompat.offsetTopAndBottom(this, 0);
}
@Override
public void offsetTopAndBottom(int offset)
{
super.offsetTopAndBottom((int) (offset + (getHeight() * 0.5f)));
}
}
layout_anchor
layout_anchorGravity
といくつかのパディングの両方を使用することで、この問題を回避することができました。アンカー属性を使用すると、別のビューに対してView
の位置自体を持つことができます。ただし、RelativeLayout
と同じようには機能しません。続きます。
layout_anchor
は、希望するView
を配置するView
を指定します(つまり、このView
は、layout_anchor
属性で指定されたView
を基準にして配置する必要があります)。次に、layout_anchorGravity
は、通常の 重力 値(View
、View
、center_horizontal
など)を使用して、現在のtop
が相対bottom
のどちら側に配置されるかを指定します。
これら2つの属性だけを使用する場合の問題は、アンカーを持つView
のcenterが他のView
に対して相対的に配置されることです。たとえば、FloatingActionButton
の下部に固定するTextView
を指定した場合、実際に起こるのは、FABのcenterがTextView
の下端に沿って配置されます。
この問題を回避するために、FABの上部エッジがTextView
の下部エッジに接触するように、FABにパディングを適用しました。
<Android.support.design.widget.FloatingActionButton
Android:id="@+id/fab"
Android:layout_width="wrap_content"
Android:layout_height="wrap_content"
Android:layout_anchor="@id/your_buttons_id_here"
Android:layout_anchorGravity="bottom"
Android:paddingTop=16dp" />
必要な効果を得るには、パディングを増やす必要がある場合があります。お役に立てば幸いです!