Фокус вверх/вниз не работает в Android Studio с FrameLayouts внутри ConstraintLayout с LeanbackAndroid

Форум для тех, кто программирует под Android
Ответить
Anonymous
 Фокус вверх/вниз не работает в Android Studio с FrameLayouts внутри ConstraintLayout с Leanback

Сообщение Anonymous »

У меня есть действие с макетом ограничений с элементом EditText, а затем двумя фреймами, в которые я добавляю RowSupportFragment и ВертикальныйSupportGridFragment программно на основе вызова API. Я получаю данные, и они отображаются на экране. Однако вы не можете перемещаться вверх/вниз. Я не знаю, п о ч е м у о н н е р а б о т а е т д о л ж н ы м о б р а з о м . < / p > < b r / > В о т м о й X M L - ф а й л м а к е т а : < / p > < b r / > < c o d e > & l t ; a n d r o i d x . c o n s t r a i n t l a y o u t . w i d g e t . C o n s t r a i n t L a y o u t x m l n s : a n d r o i d = & q u o t ; h t t p : / / s c h e m a s . a n d r o i d . c o m / a p k / r e s / a n d r o i d & q u o t ; < b r / > x m l n s : t o o l s = & q u o t ; h t t p : / / s c h e m a s . a n d r o i d . c o m / t o o l s & q u o t ; < b r / > a n d r o i d : i d = & q u o t ; @ + i d / s e a r c h L a y o u t & q u o t ; < b r / > a n d r o i d : b a c k g r o u n d = & q u o t ; # 0 0 0 0 0 0 & q u o t ; < b r / > a n d r o i d : l a y o u t _ w i d t h = & q u o t ; m a t c h _ p a r e n t & q u o t ; < b r / > a n d r o i d : l a y o u t _ h e i g h t = & q u o t ; m a t c h _ p a r e n t & q u o t ; < b r / > x m l n s : a p p = & q u o t ; h t t p : / / s c h e m a s . a n d r o i d . c o m / a p k / r e s - a u t o & q u o t ; < b r / > & g t ; < b r / > & l t ; T e x t V i e w < b r / > a n d r o i d : i d = & q u o t ; @ + i d / s e a r c h T e x t V i e w & q u o t ; < b r / > a n d r o i d : l a y o u t _ w i d t h = & q u o t ; m a t c h _ p a r e n t & q u o t ; < b r / > a n d r o i d : l a y o u t _ h e i g h t = & q u o t ; w r a p _ c o n t e n t & q u o t ; < b r / > a n d r o i d : l a y o u t _ m a r g i n S t a r t = & q u o t ; 1 6 d p & q u o t ; < b r / > a n d r o i d : l a y o u t _ m a r g i n T o p = & q u o t ; 1 6 d p & q u o t ; < b r / > a n d r o i d : t e x t = & q u o t ; S e a r c h & q u o t ; < b r / > a n droid:textColor="@color/white"
android:textSize="24sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>








Вот мой котлин-файл активности:

Код: Выделить всё

class SearchActivity:  AppCompatActivity(), SearchView {
private lateinit var binding: ActivitySearchBinding
private val disposables = CompositeDisposable()
private val presenter = SearchPresenter()
private var fManager: FragmentManager? = null
private var fragmentTransaction: FragmentTransaction? = null

private var channelList: List? = emptyList()
private var videoList: List? = emptyList()
private var channelFragment: SearchChannelRowFragment? = null
private var videoFragment: SearchVideoGridFragment? = null

override fun onCreate(bundle: Bundle?) {
super.onCreate(bundle)

presenter.attachMvpView(this)
fManager = supportFragmentManager

binding = ActivitySearchBinding.inflate(layoutInflater)
setContentView(binding.root)

setupEditTextListeners(binding.searchEditText)

channelFragment = SearchChannelRowFragment(
onItemClick = ::onChannelItemClick,
onItemSelect = ::onChannelItemSelect
)
videoFragment = SearchVideoGridFragment(
onItemClick = ::onVideoItemClick,
onItemSelect = ::onVideoItemSelect
)

fragmentTransaction = fManager?.beginTransaction()
channelFragment?.results = channelList ?: emptyList()
videoFragment?.results = videoList ?: emptyList()
fragmentTransaction?.add(R.id.searchChannelLayout, channelFragment!!)
fragmentTransaction?.add(R.id.searchVideoLayout, videoFragment!!)
fragmentTransaction?.commit()

binding.searchEditText.requestFocus()
}

private fun showKeyboard(view: View) {
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT)
}

private fun setupEditTextListeners(et: EditText) {
et.onFocusChangeListener = View.OnFocusChangeListener { view, hasFocus ->
if (hasFocus) {
showKeyboard(view)
}
}
et.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable?) {
if (s.toString().length > 2) {
val searchBody = SearchRequestBody(q = s.toString())
Log.d("SearchActivity", "Search query: ${searchBody.q}")
presenter.getSearchResultsData(searchBody)
}
}

override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
}

override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {

}
})
}

override fun getMvpContext(): Context = this

override fun showSearchResults(searchResults: SearchResultsData) {
channelList = searchResults.channel?.hits
videoList = searchResults.videos?.hits

channelFragment?.results = channelList ?: emptyList()
videoFragment?.results = videoList ?: emptyList()
Log.d("SearchActivity", "channelList: ${channelList?.size} | videoList: ${videoList?.size}")
Log.d("SearchActivity", "channelFragment: ${channelFragment?.results?.size} | videoFragment: ${videoFragment?.results?.size}")
binding.searchVideoTitle.visibility = if (videoFragment?.results?.size!! >  0) View.VISIBLE else View.INVISIBLE
Log.d("SearchActivity", "videoTitle visibility: ${binding.searchVideoTitle.visibility}")
}

private fun onChannelItemClick(videoUrl: String?, videoTitle: String?, imageURL: String?) {
Log.d("onHostItemClick pos: ", videoTitle.toString())
val hostDataObject: HostData = HostData()
hostDataObject.title = videoTitle
hostDataObject.imageUrl = imageURL
//HostDetailActivity.launchActivity(this, videoUrl, videoTitle, imageURL)
}

private fun onChannelItemSelect(itemPosition: Int?, totalSize: Int?) {
Log.d("onChannelItemSelect pos: ", itemPosition.toString())
//this.itemPosition = itemPosition;
binding.itemCountChannels.text = "${itemPosition?.plus(1)} of $totalSize"
}

private fun onVideoItemClick(itemPosition: Int?, item: SearchResultsVideoHitData) {
if (item.productId.isNullOrEmpty().not()) {
PaidMediaActivity.launchActivity(this, item.productId, item.productId, item.document?.thumbnailUrl)
}
else {
val videoUrl = item.document?.hlsUrl ?: item.document?.videoPlaybackUrl
ExoPlayerActivity.launchActivity(this, videoUrl, item.title, item.document?.thumbnailUrl, itemPosition ?: 0)
}
}

private fun onVideoItemSelect(itemPosition: Int?, item: SearchResultsVideoHitData?, totalSize: Int?) {
//this.itemPosition = itemPosition;
binding.itemCountVideos.text = "${itemPosition?.plus(1)} of $totalSize"
}

override fun onDestroy() {
disposables.clear()
super.onDestroy()
}

override fun onError(throwable: Throwable) {
Log.e("SearchActivity", "Error: ${throwable.message}")
errorView(throwable.message)
}

private fun errorView(message: String? = null) {
//binding.progressbar.visibility = android.view.View.GONE
Toast.makeText(this, message ?: "There was an error with your request.  Please try again", Toast.LENGTH_LONG).show()
//finish()
}

private fun transitionToPlayer(mediaDetails: MediaDetailsData, holdView: Boolean = false) {
val itemDetails: AllVideosData = AllVideosData()
itemDetails.videoUrl = mediaDetails.mediaUrl ?: mediaDetails.trailerUrl
itemDetails.title = mediaDetails.productTitle
itemDetails.teaserText = mediaDetails.productDescription

itemDetails.let {
ExoPlayerActivity.launchActivity(this, it.videoUrl, it.title, it.teaserText, 0)
if (!holdView) finish()
}
}

override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
// Custom Key events here
return when (event?.keyCode) {
KeyEvent.KEYCODE_BACK -> {
Log.d("KeyEvent", "KEYCODE_BACK")
finish()
true
}

KeyEvent.KEYCODE_DPAD_CENTER -> {
Log.d("KeyEvent", "KEYCODE_DPAD_CENTER")
true
}

KeyEvent.KEYCODE_DPAD_DOWN -> {
if (binding.searchChannelLayout.hasFocus()) {
Log.d("KeyEvent", "KEYCODE_DPAD_DOWN | channels have focus")
binding.searchVideoLayout.rootView.requestFocus()
true
} else if (binding.searchEditText.hasFocus()) {
binding.searchChannelLayout.requestFocus()
true
}
else {
false
}
}

KeyEvent.KEYCODE_DPAD_UP -> {
if (binding.searchVideoLayout.hasFocus()) {
channelFragment?.view?.requestFocus()
true
} else if (binding.searchChannelLayout.hasFocus()) {
binding.searchEditText.requestFocus()
true
}
else {
false
}
}

else -> {
super.onKeyDown(keyCode, event)
}
}
}

companion object {
fun launchActivity(
activity: Activity,
) {
val intent = Intent(activity, SearchActivity::class.java)
ActivityCompat.startActivity(
activity,
intent,
null
)
}
}
}
Вот мой RowSupportFragment:

Код: Выделить всё

class SearchChannelRowFragment(
var onItemClick: (String?, String?, String?) -> Unit?,
var onItemSelect: (Int, Int) -> Unit?
): RowsSupportFragment(), OnItemViewSelectedListener, OnItemViewClickedListener {

private var channelList: List? = null

private var mainAdapter: ArrayObjectAdapter? = null
private var channelListAdapter: ArrayObjectAdapter? = null

private var firstItem: SearchChannelItemCardPresenter? = null
private var firstItemPos: Int? = 0

var results: List  by Delegates.observable(emptyList()){ _, oldValue, newValue ->
if (oldValue != newValue){
Log.d("SearchVideoGridFragment", "results changed: $oldValue | $newValue")
results = newValue
displayData()
}
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

val aa = CustomListRowPresenter(FocusHighlight.ZOOM_FACTOR_NONE, false)
aa.shadowEnabled = false
aa.selectEffectEnabled = false
mainAdapter = ArrayObjectAdapter(aa)

setAlignment(60)

setOnItemViewSelectedListener { itemViewHolder, item, rowViewHolder, row ->
Log.d("Item", "Item: $item")
val listRow = row as ListRow

val currentRowAdapter = listRow.adapter as ArrayObjectAdapter
val itemPos = currentRowAdapter.indexOf(item)
if (currentRowAdapter == channelListAdapter) {
firstItemPos = itemPos
//Log.d("itemPos", "ItemPos: $itemPos")
}
onItemSelect.invoke(itemPos, listRow.adapter?.size() ?: 0)
}
setOnItemViewClickedListener { itemViewHolder, item, rowViewHolder, row ->
Log.d("setOnClickListener", "setOnClickListener $item")
val listRow = row as ListRow
val currentRowAdapter = listRow.adapter as ArrayObjectAdapter
val itemPos = currentRowAdapter.indexOf(item)
if (item is HostData){
Log.d("setOnClickListener", "setOnClickListener: ${item.title}")
// Convert SearchResultsChannelHitData to HostData to pass
// to HostDetailActivity.launchActivity
activity?.let { HostDetailActivity.launchActivity(it, item, itemPos ) }
}else if (item is AbsoluteRowData){
onItemClick.invoke(item.videoUrl, item.title, item.imageUrl)
}else if (item is RecentlyAddedRowData){
Log.e("recent item", "Item")
onItemClick.invoke(item.mediaUrl, item.title, item.imageUrl)
}
}
}

override fun onDestroy() {
super.onDestroy()
mainAdapter?.clear()
clearData()
}

override fun onDestroyView() {
super.onDestroyView()
mainAdapter?.clear()
clearData()
}

override fun onItemSelected(itemViewHolder: Presenter.ViewHolder?, item: Any?, rowViewHolder: RowPresenter.ViewHolder?, row: Row?) {
val position: Int? = mainAdapter?.indexOf(item)
if (item is SearchResultsChannelHitData){
onItemSelect.invoke(position?:0, mainAdapter?.size() ?: 0)
}
}

override fun onItemClicked(
itemViewHolder: Presenter.ViewHolder?,
item: Any?,
rowViewHolder: RowPresenter.ViewHolder?,
row: Row?
) {
Log.d("SearchChannelRowFragment", "onItemClicked")
if (item is SearchResultsVideoHitData &&  item.productId.isNullOrEmpty().not()){
Log.d("ProductID", "ProductID: ${item.productId}")
activity?.let { PaidMediaActivity.launchActivity(it,null, item.productId, item.document?.thumbnailUrl) }
}else if (item is AbsoluteRowData){
onItemClick.invoke(item.videoUrl, item.title, item.imageUrl)
}else if (item is RecentlyAddedRowData){
Log.e("recent item", "Item")
onItemClick.invoke(item.mediaUrl, item.title, item.imageUrl)
}
}

private fun displayData(){
Log.d("SearchChannelRowFragment", "displayData run ${results.size}")
// We need to mutate results into HostData objects
var hostDataList = mutableListOf()
for (result in results) {
val hostData = HostData()
hostData.title = result.title
hostData.teaserText = ""
hostData.nid = result.document?.externalId
hostData.imageUrl = result.document?.imageUrl
hostDataList.add(hostData)
}
mainAdapter?.clear()
channelListAdapter?.clear()
channelListAdapter = ArrayObjectAdapter(SearchChannelItemCardPresenter(requireContext()))
channelListAdapter?.addAll(0 ,hostDataList)

if (channelListAdapter?.size() != 0){
mainAdapter?.add(ListRow(HeaderItem("Channels"), channelListAdapter))
}

/*  if(absoluteItemList.isNullOrEmpty().not()) {
val headerFeaturedItem = HeaderItem("Recent Clips")
absoluteItemAdapter?.addAll(0, absoluteItemList)
if (absoluteItemAdapter?.size() != 0){
mainAdapter?.add(ListRow(headerFeaturedItem, absoluteItemAdapter))
}
}
*/

adapter = mainAdapter

if (firstItem == null){
firstItem = channelListAdapter?.getPresenter(0) as? SearchChannelItemCardPresenter
}
}

private fun clearData(){
channelList = emptyList()
mainAdapter?.clear()
}
}
Вот мой UpperSupportGridFragment:

Код: Выделить всё

class SearchVideoGridFragment(
val onItemClick: (index: Int?, item: SearchResultsVideoHitData) -> Unit,
val onItemSelect: (index: Int?, item: SearchResultsVideoHitData, size: Int?) -> Unit
): VerticalGridSupportFragment(), OnItemViewSelectedListener,
OnItemViewClickedListener {

private var mainAdapter: ArrayObjectAdapter? = null

private var firstItem: SearchVideoItemCardPresenter? = null
private val ZOOM_FACTOR: Int = FocusHighlight.ZOOM_FACTOR_NONE
private val COLUMNS = 6
private val deviceWidth = DisplayMetrics().widthPixels
private val cellWidth = deviceWidth / COLUMNS
private val cellHeight = cellWidth * 9 / 16

var results: List  by Delegates.observable(emptyList()){ _, oldValue, newValue ->
if (oldValue != newValue){
Log.d("SearchVideoGridFragment", "results changed")
displayData()
}
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d("SearchVideoGridFragment", "onCreate")
setupAdapter()
}

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
Log.d("SearchVideoGridFragment", "onCreateView")
val view = super.onCreateView(inflater, container, savedInstanceState)
applyConstraints(view)
return view
}

override fun onDestroy() {
super.onDestroy()
clearData()
}

override fun onDestroyView() {
super.onDestroyView()
clearData()
}

private fun applyConstraints(view: View?) {
Log.d("SearchVideoGridFragment", "applyConstraints: $view")
}

private fun setupAdapter() {
val videoGridPresenter =
CustomVerticalGridPresenter(ZOOM_FACTOR, false, 100, 1, 0,0)
videoGridPresenter.numberOfColumns = COLUMNS
videoGridPresenter.shadowEnabled = false
val itemDecoration = VerticalSpaceItemDecoration(200)
videoGridPresenter.gridView?.post {
videoGridPresenter.gridView?.addItemDecoration(itemDecoration)
}

setOnItemViewSelectedListener(this)
onItemViewClickedListener = this
gridPresenter = videoGridPresenter

val sideInfoCardPresenter =
SearchVideoItemCardPresenter(requireActivity())
mainAdapter = ArrayObjectAdapter(sideInfoCardPresenter)
adapter = mainAdapter
applyConstraints(videoGridPresenter.gridView?.rootView)
}

private fun displayData(){
mainAdapter?.clear()
mainAdapter?.addAll(0, results)

if (firstItem == null){
firstItem = mainAdapter?.getPresenter(0) as SearchVideoItemCardPresenter
}
}

private fun clearData(){
mainAdapter?.clear()
}

override fun onItemSelected(
itemViewHolder: Presenter.ViewHolder?,
item: Any?,
rowViewHolder: RowPresenter.ViewHolder?,
row: Row?
) {
Log.d("SearchVideoGridFragment", "onItemSelected")
val position: Int? = mainAdapter?.indexOf(item)
if (item is SearchResultsVideoHitData){
onItemSelect.invoke(position, item, mainAdapter?.size()?:0)
}
}

override fun onItemClicked(
itemViewHolder: Presenter.ViewHolder?,
item: Any?,
rowViewHolder: RowPresenter.ViewHolder?,
row: Row?
) {
Log.d("SearchVideoGridFragment", "onItemClicked")
val position: Int? = mainAdapter?.indexOf(item)
if (item is SearchResultsVideoHitData){
onItemClick.invoke(position, item)
} else {
Log.d("SearchVideoGridFragment", "item is: "+ item!!::class.simpleName)
}
}

}
Поскольку rowFragment представляет собой одну строку, я могу вручную управлять фокусом между строкой и searchEditText, но я действительно не хочу делать это таким образом .

Подробнее здесь: https://stackoverflow.com/questions/787 ... onstraintl
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «Android»