VerticalGridView第一行获取焦点后,再次按向上键,上方的控件获取不到焦点解决方案

本文介绍如何使用VerticalGridView实现选集功能,并解决向上键焦点丢失问题。通过自定义适配器和OnKeyListener,确保焦点平滑过渡,同时实现获取焦点时的高亮效果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近在做项目中,有这么一个需求。点击电视剧某一集进入详情页,并可以点击选集按钮进行选集。于是乎,想到了使用VerticalGridView来实现(VerticalGridView是V17新推出的控件,不熟悉的朋友可以查找资料学习下)。使用VerticalGridView很容易实现了基本的功能,但是遇到一个很棘手的问题。就是当第一行获取焦点后,再次按向上键,上方的控件获取不到焦点。在网上找了好多解决方案,最终也没有解决问题。最后,使用setOnKeyListner方法来实现。虽说解决了问题,带来了新的问题:在第二行按向上键后,第一行先获取到焦点,接着第一行有失去焦点,上方控件获取到焦点了。尼玛,我也是醉了。最终发现onKey方法执行了两次,知道了原因,问题就好解决了。下面,就介绍下实现方案。

本Demo还实现了,获取焦点高亮显示的效果。VerticalGridView的使用不在介绍,不熟悉的可以参考 

android leanback使用详解以及获取焦点高亮

首先,布局页面编写:

(1)主布局页面

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:lb="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/index_bg"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="@dimen/h_56"
        android:orientation="horizontal">

        <View
            android:id="@+id/btn_pd_play"
            style="@style/pd_btn"
            android:background="@drawable/pd_play_selector"
            android:visibility="visible" />

        <View
            android:id="@+id/btn_pd_collect"
            style="@style/pd_btn.non_starter"
            android:background="@drawable/pd_collect_selector"
            android:visibility="visible" />

        <View
            android:id="@+id/btn_pd_xuanji"
            style="@style/pd_btn.non_starter"
            android:visibility="visible" />
    </LinearLayout>

    <android.support.v17.leanback.widget.HorizontalGridView
        android:id="@+id/hv_tuijian"
        android:layout_width="match_parent"
        android:layout_height="@dimen/h_400"
        android:layout_marginLeft="@dimen/w_145"
        android:layout_marginRight="@dimen/w_115"
        android:focusable="true"
        android:focusableInTouchMode="true"
        lb:horizontalMargin="@dimen/w_15"
        lb:numberOfRows="1"></android.support.v17.leanback.widget.HorizontalGridView>

    <android.support.v17.leanback.widget.VerticalGridView
        android:id="@+id/hv_suanji"
        android:layout_width="match_parent"
        android:layout_height="@dimen/h_480"
        android:layout_marginLeft="@dimen/w_145"
        android:layout_marginRight="@dimen/w_115"
        android:focusable="true"
        android:focusableInTouchMode="true"
        android:paddingTop="@dimen/h_10"
        lb:horizontalMargin="@dimen/w_15"
        lb:numberOfColumns="10"
        lb:numberOfRows="2"></android.support.v17.leanback.widget.VerticalGridView>

</LinearLayout>

(2)item布局页面

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/ll_item_search"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:clipChildren="false"
    android:clipToPadding="false"
    android:focusable="true"
    android:focusableInTouchMode="true"
    android:orientation="vertical" >

    <RelativeLayout
        android:id="@+id/rl_scale"
        android:layout_width="@dimen/w_220"
        android:layout_height="@dimen/h_260"
        android:background="@drawable/bg_pic_n" >

        <ImageView
            android:id="@+id/iv_pic"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@drawable/mid_bottom"
            android:scaleType="fitXY" />

        <TextView
            android:id="@+id/tv_target"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignBottom="@id/iv_pic"
            android:textColor="@color/search_text"
            android:gravity="center"
            android:padding="@dimen/w_10"
            android:text="更新至"
            android:textSize="@dimen/w_30"
            android:visibility="gone" />
    </RelativeLayout>

    <FrameLayout
        android:layout_width="@dimen/w_220"
        android:layout_height="wrap_content"
        android:layout_below="@id/rl_scale"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="@dimen/h_2" >

        <TextView
            android:id="@+id/tv_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:singleLine="true"
            android:textColor="@color/search_text"
            android:textSize="@dimen/w_32" />
    </FrameLayout>

</RelativeLayout>

接着,编写通用的适配器:

package cn.chinaiptv.foucedemo.adapter;

import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import java.util.Collections;
import java.util.Comparator;
import java.util.List;

/**
 * @author zeng_yong_chang@163.com
 */
public abstract class RvAdapter<T, VH extends RecyclerView.ViewHolder> extends
        RecyclerView.Adapter<VH> {
    protected List<T> mList;

    public RvAdapter(List<T> data) {
        setData(data);
    }

    public List<T> getData() {
        return mList;
    }

    public void setData(List<T> data) {
        mList = data;
        notifyDataSetChanged();
    }

    public void sort(Comparator<T> comparator) {
        Collections.sort(mList, comparator);
        notifyDataSetChanged();
    }

    protected View inflateView(int itemViewLayoutId, ViewGroup parent) {
        return LayoutInflater.from(parent.getContext()).inflate(
                itemViewLayoutId, parent, false);
    }

    @Override
    public int getItemCount() {
        return mList.size();
    }
}

其次,VerticalGridView适配器的编写,具体代码如下:
package cn.chinaiptv.foucedemo.adapter;

import android.annotation.SuppressLint;
import android.content.Context;
import android.support.v17.leanback.widget.VerticalGridView;
import android.support.v7.widget.RecyclerView.Adapter;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import java.util.List;

import cn.chinaiptv.foucedemo.R;
import cn.chinaiptv.foucedemo.bean.Content;
import cn.chinaiptv.foucedemo.cn.chinaiptv.foucedemo.holder.SearchListViewHolder;
import cn.chinaiptv.foucedemo.utils.FocusUtisl;

@SuppressLint("InflateParams")
public class XuanJiAdapter extends
        Adapter<SearchListViewHolder> {

    private List<Content> contentList;

    private Context context;

    private VerticalGridView hv_suanji;
    private View btn_pd_xuanji;

    public XuanJiAdapter(List<Content> recommenList, VerticalGridView hv_suanji, View btn_pd_xuanji) {
        contentList = recommenList;
        this.hv_suanji = hv_suanji;
        this.btn_pd_xuanji = btn_pd_xuanji;
    }

    @Override
    public int getItemCount() {
        return contentList.size();

    }

    @Override
    public void onBindViewHolder(final SearchListViewHolder holder, final int position) {
        holder.itemView.setOnClickListener(new FocusUtisl.MyClick(position));
        holder.itemView.setOnFocusChangeListener(new FocusUtisl.MyFocuchange(holder.rl_scale, holder.tv_name,
                position));
        String name = "";
        String miniPicURI = "";
        Content content = contentList.get(position);
        name = content.getName();
        miniPicURI = content.getMiniPicURI();
        holder.tv_name.setText(name);
    }

    @Override
    public SearchListViewHolder onCreateViewHolder(ViewGroup parent, int arg1) {
        View inflate = LayoutInflater.from(parent.getContext()).inflate(
                R.layout.item_serialdetail_recomm, null);
        SearchListViewHolder holder = new SearchListViewHolder(inflate);
        context = parent.getContext();
        return holder;
    }

}

接下来就要在Activity中设置数据了,代码如下:

package cn.chinaiptv.foucedemo;

import android.app.Activity;
import android.os.Bundle;
import android.support.v17.leanback.widget.HorizontalGridView;
import android.support.v17.leanback.widget.VerticalGridView;
import android.view.View;

import java.util.ArrayList;

import cn.chinaiptv.foucedemo.adapter.SerialDetailRecommAdapter;
import cn.chinaiptv.foucedemo.adapter.XuanJiAdapter;
import cn.chinaiptv.foucedemo.bean.Content;

/**
 * 连续剧详情页
 */
public class MainActivity extends Activity {

    private  HorizontalGridView hv_tuijian;
    private VerticalGridView hv_suanji;
    private  XuanJiAdapter xuanjiAdapter;
    private View btn_pd_xuanji;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_series_detail);
        btn_pd_xuanji=findViewById(R.id.btn_pd_xuanji);
        initView();
    }

    private void initView() {
        hv_tuijian=(HorizontalGridView)findViewById(R.id.hv_tuijian);
        hv_suanji=(VerticalGridView)findViewById(R.id.hv_suanji);
        getListRecommen();
        getXuanJi();

    }
    private  ArrayList<Content> recommenList;
    private  ArrayList<Content> xuanJiList;
    private SerialDetailRecommAdapter recommenResultAdapter;
    /**
     * 获取可能要找的内容
     */
    private void getListRecommen() {
        recommenList=new ArrayList<Content>();
        for(int i=0;i<10;i++){
            Content content=new Content();
            content.setName("十月风暴"+i);
            content.setType("ddd");
            recommenList.add(content);
        }
        recommenResultAdapter = new SerialDetailRecommAdapter(recommenList);
        hv_tuijian.setAdapter(recommenResultAdapter);
    }
    /**
     * 获取可能要找的内容
     */
    private void getXuanJi() {
        xuanJiList=new ArrayList<Content>();
        for(int i=0;i<20;i++){
            Content content=new Content();
            content.setName("测试"+i);
            content.setType("ddd");
            xuanJiList.add(content);
        }
        xuanjiAdapter = new XuanJiAdapter(xuanJiList,hv_suanji,btn_pd_xuanji);
        hv_suanji.setAdapter(xuanjiAdapter);
    }
}


如果只这样去实现,第一行获取焦点后,再按向上键,上方的控件是获取不到代码的。解决方案如下:
  @Override
    public void onBindViewHolder(final SearchListViewHolder holder, final int position) {
        holder.itemView.setOnClickListener(new FocusUtisl.MyClick(position));
        holder.itemView.setOnFocusChangeListener(new FocusUtisl.MyFocuchange(holder.rl_scale, holder.tv_name,
                position));
        String name = "";
        String miniPicURI = "";
        Content content = contentList.get(position);
        name = content.getName();
        miniPicURI = content.getMiniPicURI();
        holder.tv_name.setText(name);
        holder.itemView.setOnKeyListener(new View.OnKeyListener() {
            @Override
            public boolean onKey(View v, int keyCode, KeyEvent event) {
                if (event.getAction() == KeyEvent.ACTION_UP) {
                    return true;
                } else if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
                    if (position < 10) {
                        hv_suanji.setFocusable(false);
                        btn_pd_xuanji.requestFocus();
                    }
                }
                return false;
            }
        });

    }
这样上方的控件就可以获取到焦点了。

最后,还有一些获取焦点时,高亮显示的方法,代码如下:

package cn.chinaiptv.foucedemo.utils;

import android.view.View;

import cn.chinaiptv.foucedemo.R;

/**
 * Created by ddklsy163com on 17/1/13.
 */

public class FocusUtisl {
    private static OnItemCallBack onItemCallBack;

    public static class MyFocuchange implements View.OnFocusChangeListener {

        int position;
        View rl_scale;
        View tv_name;

        /**
         * 图片,文字都放缩
         *
         * @param position
         * @param rl_scale
         */
        public MyFocuchange(View rl_scale, View tv_name, int position) {
            this.position = position;
            this.rl_scale = rl_scale;
            this.tv_name = tv_name;
//            Log.e("111111",position);
        }

        /**
         * 仅仅放缩图片
         *
         * @param position
         * @param rl_scale
         */
        public MyFocuchange(int position, View rl_scale) {
            this.position = position;
            this.rl_scale = rl_scale;
        }

        @Override
        public void onFocusChange(View v, boolean hasFocus) {
            if (hasFocus) {
                if (rl_scale != null) {
                    rl_scale.animate().scaleX(1.1f).scaleY(1.1f).start();
                    rl_scale.setBackgroundResource(R.drawable.bg_pic_s);
                }
                if (tv_name != null)
                    tv_name.animate().scaleX(1.1f).scaleY(1.1f).start();
            } else {
                if (rl_scale != null) {
                    rl_scale.animate().scaleX(1.f).scaleY(1.f).start();
                    rl_scale.setBackgroundResource(R.drawable.bg_pic_n);
                }
                if (tv_name != null)
                    tv_name.animate().scaleX(1f).scaleY(1f).start();

            }
            if (onItemCallBack != null) {
                onItemCallBack.onFocusChange(v, hasFocus, position);
            }
        }

    }

    public static class MyClick implements View.OnClickListener {

        int position;

        public MyClick(int position) {
            this.position = position;
        }

        @Override
        public void onClick(View v) {
            if (onItemCallBack != null) {
                onItemCallBack.onItemClick(v, position);
            }
        }
    }

    public void setOnItemCallBack(OnItemCallBack onItemCallBack) {
        this.onItemCallBack = onItemCallBack;
    }

    public interface OnItemCallBack {
        public void onFocusChange(View v, boolean hasFocus, int posiotion);

        public void onItemClick(View v, int position);
    }
}


到此,已经完美实现了获取焦点时高亮显示,并且解决了上方控件获取不到焦点的bug,Demo截图如下:



源码点击下载

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

飞猫个人博客

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值