Android TV item 选中 放大,加边框

Android Tv 的交互是通过遥控器来进行的,焦点移动是用户操作后的直观感受。如何让用户直观的操控Tv是本文的重点介绍内容。
1. Tv开发一般都会有自定义的Launcher,launcher中显示媒体资源数据。类似下图
这里写图片描述
每个item选中后会有个边框,并且会变大。下面将如何实现这个效果。

放大效果

首先item的布局,以LinearLayout为例,我们要的效果是当LinearLayout或者Imageview 获取焦点时, LinearLayout放大,当焦点移动到其他item时,此item丢失焦点,恢复原来的大小。所以我们自定义LinearLayout。

public class CustomLinearLayout extends LinearLayout implements View.OnFocusChangeListener, CustomViewInterface, View.OnHoverListener {
    CustomFocusedChanged focusedChanged;

    public CustomLinearLayout(Context context) {
        super(context);
        init();
    }

    public CustomLinearLayout(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public CustomLinearLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    public CustomLinearLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init();
    }

    public void setFocusedChanged(CustomFocusedChanged focusedChanged) {
        this.focusedChanged = focusedChanged;
    }

    @Override
    public void init() {
        this.setOnFocusChangeListener(this);
        this.setOnHoverListener(this);
    }

    @Override
    public void onFocusChange(View v, boolean hasFocus) {
        if (focusedChanged != null) {
            focusedChanged.onFocusChange(v, hasFocus);//外部接口调用
        }
        DisplayUtil.setViewAnimator(v, hasFocus);//放大缩小动画
    }
    //鼠标事件处理
    @Override
    public boolean onHover(View v, MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_HOVER_ENTER) {
            DisplayUtil.setViewAnimator(v, true);
        } else if (event.getAction() == MotionEvent.ACTION_HOVER_EXIT) {
            DisplayUtil.setViewAnimator(v, false);
        }
        return false;
    }
}

接口介绍

public interface CustomViewInterface {
    void init();

    void setFocusedChanged(CustomFocusedChanged focusedChanged);
}

此接口作用是因为我们的自定义控件获取焦点,当外部需要监听其焦点变化时,若再调用View.OnFocusChangeListener接口会覆盖掉我们自定义控件中的监听,所有再写一个接口供外部调用。

View.OnHoverListener接口未监听鼠标接口
实现onHover 方法

@Override
    public boolean onHover(View v, MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_HOVER_ENTER) {
            //鼠标进入控件范围
        } else if (event.getAction() == MotionEvent.ACTION_HOVER_EXIT) {
            //鼠标离开控件
        }
        return false;
    }

放大动画

public static final float ZOOM_SCALE = 1.05f;
private static final float ORIGIN_SCALE = 1.0f;
 public static void setViewAnimator(View v, boolean focus) {
        setViewAnimator(v, focus, ZOOM_SCALE);
    }

    public static void setViewAnimator(View v, boolean focus, float... params) {
        AnimatorSet animatorSet = new AnimatorSet();//组合动画
        ObjectAnimator scaleX;
        ObjectAnimator scaleY;
        if (focus) {
            scaleX = ObjectAnimator.ofFloat(v, "scaleX", ORIGIN_SCALE, params[0]);
            scaleY = ObjectAnimator.ofFloat(v, "scaleY", ORIGIN_SCALE, params[0]);
        } else {
            scaleX = ObjectAnimator.ofFloat(v, "scaleX", params[0], ORIGIN_SCALE);
            scaleY = ObjectAnimator.ofFloat(v, "scaleY", params[0], ORIGIN_SCALE);
        }
        if (params.length > 1) {
            v.setPivotX(params[1]);
            v.setPivotY(params[2]);
        }
        animatorSet.setDuration(300);
        animatorSet.setInterpolator(new DecelerateInterpolator());
        animatorSet.play(scaleX).with(scaleY);//两个动画同时开始
        animatorSet.start();
    }

圆角效果

重点类:RoundDrawable,ColorStateList

public class RoundLinearLayout extends LinearLayout{

    private ColorStateList mSolidColor;
    private int mCornerRadius;
    public RoundLinearLayout(Context context) {
        super(context);
    }

    public RoundLinearLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public RoundLinearLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    public RoundLinearLayout(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RoundButton);
        float pressedRatio = a.getFloat(R.styleable.RoundButton_btnPressedRatio, 0.80f);
        mCornerRadius = a.getLayoutDimension(R.styleable.RoundButton_btnCornerRadius, getResources().getDimensionPixelSize(R.dimen.radius_default));
        mSolidColor = a.getColorStateList(R.styleable.RoundButton_btnSolidColor);
        int strokeColor = a.getColor(R.styleable.RoundButton_btnStrokeColor, Color.GRAY);
        int strokeWidth = a.getDimensionPixelSize(R.styleable.RoundButton_btnStrokeWidth, 0);
        int strokeDashWidth = a.getDimensionPixelSize(R.styleable.RoundButton_btnStrokeDashWidth, 0);
        int strokeDashGap = a.getDimensionPixelSize(R.styleable.RoundButton_btnStrokeDashGap, 0);
        a.recycle();
        RoundDrawable rd = new RoundDrawable(mCornerRadius == -1);
        rd.setCornerRadius(mCornerRadius == -1 ? 0 : mCornerRadius);
        rd.setStroke(strokeWidth, strokeColor, strokeDashWidth, strokeDashGap);

        if (mSolidColor == null) {
            mSolidColor = ColorStateList.valueOf(Color.GRAY);
        }
        if (mSolidColor.isStateful()) {
            rd.setSolidColors(mSolidColor);
        } else if (pressedRatio > 0.0001f) {
            rd.setSolidColors(csl(mSolidColor.getDefaultColor(), pressedRatio));
        } else {
            rd.setColor(mSolidColor.getDefaultColor());
        }
        setBackground(rd);
    }

    public void setSolidColor(int color) {
        RoundDrawable rd = new RoundDrawable(mCornerRadius == -1);
        rd.setCornerRadius(mCornerRadius == -1 ? 0 : mCornerRadius);
        mSolidColor = ColorStateList.valueOf(color);
        if (mSolidColor.isStateful()) {
            rd.setSolidColors(mSolidColor);
        }  else {
            rd.setColor(mSolidColor.getDefaultColor());
        }
        setBackground(rd);
    }

    int darker(int color, float ratio) {
        color = (color >> 24) == 0 ? 0x22808080 : color;
        float[] hsv = new float[3];
        Color.colorToHSV(color, hsv);
        hsv[2] *= ratio;
        return Color.HSVToColor(color >> 24, hsv);
    }

    ColorStateList csl(int normal, float ratio) {
        //        int disabled = greyer(normal);
        int pressed = darker(normal, ratio);
        int[][] states = new int[][]{{android.R.attr.state_pressed}, {}};
        int[] colors = new int[]{pressed, normal};
        return new ColorStateList(states, colors);
    }

RoundDrawable

public class RoundDrawable extends GradientDrawable {
    private boolean mIsStadium = false;

    private ColorStateList mSolidColors;
    private int mFillColor;

    public RoundDrawable(boolean isStadium) {
        mIsStadium = isStadium;
    }

    public void setSolidColors(ColorStateList colors) {
        mSolidColors = colors;
        setColor(colors.getDefaultColor());
    }

    @Override
    protected void onBoundsChange(Rect bounds) {
        super.onBoundsChange(bounds);
        if (mIsStadium) {
            RectF rect = new RectF(getBounds());
            setCornerRadius((rect.height() > rect.width() ? rect.width() : rect.height()) / 2);
        }
    }

    @Override
    public void setColor(int argb) {
        mFillColor = argb;
        super.setColor(argb);
    }

    @Override
    protected boolean onStateChange(int[] stateSet) {
        if (mSolidColors != null) {
            final int newColor = mSolidColors.getColorForState(stateSet, 0);
            if (mFillColor != newColor) {
                setColor(newColor);
                return true;
            }
        }
        return false;
    }

    @Override
    public boolean isStateful() {
        return super.isStateful() || (mSolidColors != null && mSolidColors.isStateful());
    }
}   

attrs文件

<declare-styleable name="RoundButton">
        <!-- 背景色 -->
        <attr name="btnSolidColor" format="color"/>
        <!-- 边框色 -->
        <attr name="btnStrokeColor" format="color"/>
        <!-- 边框厚度 -->
        <attr name="btnStrokeWidth" format="dimension"/>
        <!-- 边框虚线长度 -->
        <attr name="btnStrokeDashWidth" format="dimension"/>
        <!-- 边框虚线间隙 -->
        <attr name="btnStrokeDashGap" format="dimension"/>
        <!-- 圆角半径,stadium 表示半径为 min(height,width) / 2-->
        <attr name="btnCornerRadius" format="dimension">
            <enum name="stadium" value="-1"/>
        </attr>
        <attr name="btnPressedRatio" format="float"/>
    </declare-styleable>

设置边框

创建item_background_focus.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <stroke
        android:width="@dimen/cibn_view_stroke_width"
        android:color="@color/item_stroke_focus_color"/>
    <corners
        android:bottomLeftRadius="@dimen/radius_default"//圆角与RoundLinearLayout中的角度一致
        android:bottomRightRadius="@dimen/radius_default"
        android:topLeftRadius="@dimen/radius_default"
        android:topRightRadius="@dimen/radius_default"/>
    <padding
        android:bottom="@dimen/cibn_view_stroke_padding"
        android:left="@dimen/cibn_view_stroke_padding"
        android:right="@dimen/cibn_view_stroke_padding"
        android:top="@dimen/cibn_view_stroke_padding"/>
</shape>

创建item_background_normal.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <stroke
        android:width="@dimen/cibn_view_stroke_width"
        android:color="@color/item_stroke_color"/>
    <corners
        android:bottomLeftRadius="@dimen/radius_default"
        android:bottomRightRadius="@dimen/radius_default"
        android:topLeftRadius="@dimen/radius_default"
        android:topRightRadius="@dimen/radius_default"/>
    <padding
        android:bottom="@dimen/cibn_view_stroke_padding"
        android:left="@dimen/cibn_view_stroke_padding"
        android:right="@dimen/cibn_view_stroke_padding"
        android:top="@dimen/cibn_view_stroke_padding"/>
</shape>

创建item_sleclected.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/item_background_focus" android:state_focused="true"/>
    <item android:drawable="@drawable/item_background_normal" android:state_focused="false"/>
</selector>

设置item的背景为item_sleclected.xml即可
将自定义的LinearLayout 继承RoundLinearLayout ,即为最终控件。将布局文件中的LinearLayout替换成我们的自定义LinearLayout即为最终效果。(前提是焦点问题要处理好,焦点处理需要注意的点后面会单独介绍)

您可以通过为ListView的单个单元格(也称为ListView的列表项)定义一个自定义布局来为其添边框。以下是一个示例布局文件: ```xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:background="@drawable/list_item_border"> <!-- 添背景,即边框 --> <!-- 添其他视图,例如文本、图像等 --> <TextView android:id="@+id/item_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="List Item Text" android:padding="10dp"/> </LinearLayout> ``` 在此示例布局中,我们使用LinearLayout作为根布局,并为其设置了一个背景(@drawable/list_item_border),该背景定义了单元格的边框。您可以使用其他布局替换LinearLayout,具体取决于您的需求。 然后,您需要在ListView的适配器中使用此自定义布局。例如,如果您使用ArrayAdapter,您可以按以下方式修改其构造函数: ```java ArrayAdapter<String> adapter = new ArrayAdapter<String>( this, R.layout.list_item, R.id.item_text, data); ``` 这里,我们使用R.layout.list_item作为布局文件的资源ID,并使用R.id.item_text作为文本视图的资源ID。您需要将这些资源ID替换为您自己的布局文件中的相应ID。 请注意,您还需要在list_item_border.xml中定义边框的样式。以下是一个示例样式: ```xml <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <stroke android:width="1dp" android:color="#000000"/> <padding android:left="5dp" android:right="5dp" android:top="5dp" android:bottom="5dp"/> </shape> ``` 在此样式中,我们使用stroke元素定义了边框的宽度和颜色,并使用padding元素定义了单元格的内边距。您可以根据需要修改这些值。 希望这可以帮助您添ListView单元格的边框
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值